1、Webkit模块javascript
用到的第三方库以下:css
cairohtml |
一个2D绘图库java |
casqtnode |
Unicode处理用的库,从QT中抽取部分代码造成的web |
expatsql |
一个XML SAX解析器的库chrome |
freetype数据库 |
矢量字库接口库,用于存取ttf矢量字体文件设计模式 |
libcurl |
一个开源的url库,支持HTTP、FTP等协议 |
Libjpeg,libpng |
图像解码库 |
libxml |
基于DOM树的XML解析器 |
libxslt |
XML transform engine |
pthread |
Pthread库, port of the POSIX thread library |
sqlite3 |
一个小型的数据库,据称在型入式平台是存取速度最快的数据库。 开源, 编译后就一个400K的 sqlite.dll。 移植很是方便,纯C写的。 |
wceshunt |
一个用于Windows CE平台下的C经常使用函数封装库 |
Zlib |
Zlib库。用于解压缩。 |
2. Webkit 源代码由三大模块组成:
1). WebCore,
2). WebKit,
3). JavaScriptCore。
WebCore:排版引擎核心,WebCore包含主要如下模块:Loader, Parser(DOM,Render), Layout,Paint。
WebKit:移植层,主要包含: GUI,File System, Thread,Text,图片编解码等与平台相关的函数。
JavaScriptCore:JS虚拟机,相对独立,主要用于操做DOM, DOM是W3C定义的规范,主要用于定义外部能够操做的浏览器内核的接口,而webcore必须实现DOM规范。
(具体的DOM规范能够查w3c.)
3. WebKit分模块介绍(这里简单列出,后面再具体介绍)
Webkit平台相关
1) CURL网络库
2) libPng, LibJpeg图形处理相关
3) sqlite小型关系数据库
WebCore核心
1) Loader加载资源及Cache 实现(Curl)
2) DOM : HTML词法分析与语法分析
3) DOM : DOM节点与Render节点建立,造成DOM树
4) Render:Render树介绍,RenderBox
5) Layout:排版介绍
6) Css Parser模块
7) Binding-DOM与JavascriptCore绑定的功能
JavascriptCore-javascript引擎
1) API-基本javascript功能
2) Binding与其它功能绑定的功能,如:DOM,C,JNI
3) DerviedSource自动产生的代码
4) PCRE-Perl-Compatible Regular Expressions
5) KJS-Javascript Kernel
4. 页面的整个处理流程—(简单介绍,详细流程在后面笔记中)
1). 用户输入网址后,FrameLoader::load函数会接收到URL。
2). 把URL 请求传给CURL库。
3). CURL发出http请求,获得数据后,传给Loader,开始解析。
4). 经过Dom Builder按W3C的html规范生成Dom树
5). 若是有javascript,JSEngine就经过ECMA-262标准完善Dom树
6). 在生成DOM树的同时, 同步生成Render树。
7). 解析完后, 调用Layout排版
8). Paint出来
2、libCurl库介绍
前面有说道webkit仅仅是一个页面排版的引擎,因此,对webkit来讲,网页数据(html文件,图片,.css,.js文件)的请求与接收都是经过第三方的库:libCurl来处理。
打开webkit开发工程(.sln)便可以看到,libcurl能够被静态或动态连接到主工程中。
Libcurl就是指的curl,只是在webkit工程中,不做为单独的进程存在,而是被编译成动态库。
webkit主要用到curl的如下功能:
1) Http协议。包含:Get, put, Post, Cookie管理。
2) https协议。
3) 本地文件缓存。(前进,后退管理)
Webkit具体调用了哪些curl接口,详见后面Loader模块介绍章节。这里简单列举:
1) curl_global_init(CURL_GLOBAL_ALL);
2) curl_multi_init()
3) curl_share_init()
4) curl_share_setopt()
5) curl_easy_getinfo()
6) curl_multi_fdset()
7) curl_multi_perform()
8) curl_multi_info_read()
9) curl_multi_cleanup()
10) curl_share_cleanup()
11) curl_global_cleanup();
能够看到,因为webkit要支持同时请求多个http数据,因此用到的是curl的multi接口。
在介绍Loader以前,先介绍一下libcurl,打下基础。
如下附一篇libcurl的介绍:
1、 概念
1. 为何要使用libcurl
1) 做为http的客户端,能够直接用socket链接服务器,而后对到的数据进行http解析,但要分析协议头,实现代理…这样太麻烦了。
2) libcurl是一个开源的客户端url传输库,支持FTP,FTPS,TFTP,HTTP,HTTPS,GOPHER,TELNET,DICT,FILE和LDAP,支持Windows,Unix,Linux等平台,简单易用,且库文件占用空间不到200K
2. get和post方式
客户端在http链接时向服务提交数据的方式分为get和post两种
1) Get方式将所要传输的数据附在网址后面,而后一块儿送达服务器,它的优势是效率比较高;缺点是安全性差、数据不超过1024个字符、必须是7位的ASCII编码;查询时常常用此方法。
2) Post经过Http post处理发送数据,它的优势是安全性较强、支持数据量大、支持字符多;缺点是效率相对低;编辑修改时多使用此方法。
3. cookie与session
1) cookie
cookie是发送到客户浏览器的文本串句柄,并保存在客户机硬盘上,能够用来在某个Web站点会话之间持久地保持数据。cookie在客户端。
2) session
session是访问者从到达某个特定主页到离开为止的那段时间。每一访问者都会单独得到一个session,实现站点多个用户之间在全部页面中共享信息。session在服务器上。
3) libcurl中使用cookie
保存cookie, 使以后的连接与此连接使用相同的cookie
a) 在关闭连接的时候把cookie写入指定的文件
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, "/tmp/cookie.txt");
b) 取用如今有的cookie,而不从新获得cookie
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "/tmp/cookie.txt");
b) http与https的区别
1) Http是明文发送,任何人均可以拦截并读取内容
2) Https是加密传输协议,用它传输的内容都是加密过的,https是http的扩展,其安全基础是SSL协议
c) base64编码
1) 为何要使用base64编码
若是要传一段包含特殊字符比较多的数据,直接上传就须要处理转意符之类的不少问题,用base64编码,它能够把数据转成可读的字串,base64由a-z, A-Z, +/总计64个字符组成。
2) 传送base64编码的注意事项
因为base64的组成部分有加号,而加号是url中的转意字符,因此不管是get方式仍是post,传到服务器的过程当中,都会把加号转成空格,因此在传base64以前须要把base64编码后的加号替换成”%2B”,这样就能够正常发送了。
2、 例程
d) 代码
#include <stdio.h>
#include <curl/curl.h>
bool getUrl(char *filename)
{
CURL *curl;
CURLcode res;
FILE *fp;
if ((fp = fopen(filename, "w")) == NULL) // 返回结果用文件存储
return false;
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Accept: Agent-007");
curl = curl_easy_init(); // 初始化
if (curl)
{
curl_easy_setopt(curl, CURLOPT_PROXY, "10.99.60.201:8080");// 代理
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);// 改协议头
curl_easy_setopt(curl, CURLOPT_URL, "http://www.google.com/search?hl=en&q=xieyan0811&btnG=Google+Search&aq=f&oq=xieyan081");
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
res = curl_easy_perform(curl); // 执行
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
fclose(fp);
return true;
}
bool postUrl(char *filename)
{
CURL *curl;
CURLcode res;
FILE *fp;
if ((fp = fopen(filename, "w")) == NULL)
return false;
curl = curl_easy_init();
if (curl)
{
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "/tmp/cookie.txt"); // 指定cookie文件
// curl_easy_setopt(curl, CURLOPT_COOKIEJAR, "/tmp/cookie.txt");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "&logintype=uid&u=xieyan&psw=xxx86"); // 指定post内容
curl_easy_setopt(curl, CURLOPT_PROXY, "10.99.60.201:8080");
curl_easy_setopt(curl, CURLOPT_URL, "http://mail.sina.com.cn/cgi-bin/login.cgi"); // 指定url
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
fclose(fp);
return true;
}
int main(void)
{
getUrl("/tmp/get.html");
postUrl("/tmp/post.html");
}
e) 编译
g++ main.cpp -o main -lcurl
3、Loader模块介绍
前面说过, webkit只是一个排版引擎,在Webkit排版/渲染一个网页以前, 它确定须要从网络上、或者本地文件系统中读到网页的http数据,对吧,对webkit来说,他要的就是数据,无论你是从网络读的仍是本地文件读的。
Loader就是这样一个模块,它承上启下,不只负责为webkit引擎提供数据,还控制着webkit的绘制。另外,它同时还与提供数据的“来源”打交道。
先简单举例说明:
用户输入一个url,这时是Loader接收url请求,它把url传递给curl,设置curl的回调函数,当curl读到数据,loader把数据传递给Parser,开始生成DOM。
一.下面重点介绍一下与Loader相关的数据结构和模块。
Frame:能够看作是浏览器外壳调用Loader的总入口,它就像咱们印象中的一个网页,它关注的是页面的显示 (FrameView) 、页面数据的加载(FrameLoader) 、页面内的各类控制器 (Editor, EventHandler, ScriptController, etc.) 等等,它包含如下模块(只列出重点):
Document
Page
FrameView
RenderView
FrameLoader
DOMWindow
下面分别介绍(PS: 必需要了解这些概念,否则后面的东东都没法理解):
1)Document:这个类的爷爷类是 Node ,它是 DOM 树各元素的基类; Document 有个子类是 HTMLDocument ,它是整个文档 DOM 树的根结点,这样就明白了:原来 Document 就是描述具体文档的代码,看一下它的头文件,就更明白了,它的属性与方法就是围绕着各类各样的结点: Text, Comment , CDATASection , Element……
2)Page: 个人理解是,Page与webview外部接口相关, page与frame是一对多的关系,同时Frame与FrameView是一一对应的,Frameview关注UI,Page关注数据与接口。如今的浏览器通常都提供同时打开多个窗口,每个窗口对应的数据就是这个Page在管理了。
在 page.cpp 文件里,还有个重要的全局指针变量: static HashSet<Page*>* allPages; 这个变量包含了全部的page 实例。
3)FrameView: 能够理解为为一个网页的ViewPort, 它提供一个显示区域,同时包含的有Render根节点、layout排版相关接口、Scroll相关等。FrameView是Layout排版的总入口。
4)RenderView: 与FrameView差很少,只是分工不一样,它管理与Render树相关的东东。
5) FrameLoader:重点,FrameLoader类将Documents加载到Frames。当点击一个连接时,FrameLoader建立一个新的处于“policy”状态的DocumentLoader对象,一旦webkit指示FrameLoader将本次加载视为一个导航(navigation),FrameLoader就推进DocumentLoader进入“provisional”状态,(在该状态,DocumentLoader发调用CURL发起一个网络请求,并等待是html仍是下载文件。)同时, DocumentLoader会建立一个MainResourceLoader对象(该对象在后面单独介绍)。
6)。DOMWindow: 实现了Dom的一些接口,如CreateNode等。后面能够详细讲讲。
上面介绍的概念比较多,我也不晓得好很差理解,没理解也不怕,多去看看代码,这是必须的。
2、Webkit的Loader有两条加载数据的主线
1. MainResourceLoader: 该模块主要加载主网页请求。后面称为MainResource。
2. DocLoader: 该模块除了主网页外的全部子请求,如:.js文件,图片资源,.css文件。 后面称为SubResource。
MainResource部分:
FrameLoader->DocumentLoader->MainResourceLoader-ResourceHandle DocumentLoader经历状态:1)"policy" 2) "provisional" 3) "commited"分别是等待、做为navigation发送network request、文件下载完毕
Subresource部分:
DocLoader->Cache->[CacheObjects] CacheImage->SubresourceLoader->ResourceHandle 。当请求一个资源时,首先查看Cache中是否存在该对象,若是存在直接返回;若是不存在,建立该Cache对象(如CacheImage),而后建立一个SubresourceLoader,加载资源。
举例说明:
加载图片时, DocLoader首先询问Cache, 在内存中是否也存在(CachedImage对象),若是已存在,则直接加载,即省了时间又省了流量。若是图片不在Cache中,Cache首先建立一个新的CachedImage对象来表明该图片,而后由CachedImage对象调用Loader对象发起一个网络请求,Loader对象建立SubResourceLoader。后面的流程就同样了,SubResourceLoader也是直接把ResourceHandle打交道的。
接下来跟踪一下Loader发送请求的代码实现:
1. 用户输入URL后,最早调用的接口是:
FrameLoader::load(const ResourceRequest& request)
ResourceRequest包含了:
KURL(处理url的一个类)、setHTTPHeaderField、setHTTPContentType等与HTTP头部相关的函数
2. Load()经过ResourceRequest数据调用createDocumentLoader(request, substituteData)来建立一个DocumentLoader。
3.Load()函数继续给request设置HttpAccept, Cache-Control HTTP头等信息。
4. 设置FrameLoader::checkNavigationPolicy函数进入"Policy"状态。
5.判断该url是否在Cache中等一系列状态判断后,进入DocumentLoader::startLoadingMainResource函数准备加载MainResource。该函数首先会建立调用MainResourceLoader。
6.进入MainResourceLoader::load函数,调用illSendRequest(r, ResourceResponse());作发送请求前的准备。
7.调用PolicyCheck检查policy的状态后,进入 FrameLoader::callContinueLoadAfterNavigationPolicy继续往下走。
8:在MainResourceLoader::loadNow(ResourceRequest& r)函数里建立ResourceHandle,在建立ResourceHandle函数中,调用start函数,start函数把ResourceHandle自已添加到ResourceHandleManager的m_resourceHandleList队列里。
同时,调用m_downloadTimer.startOneShot激活网页请求下载的定时器。(这是个毫秒级的定时器,采用定时器的缘由也是为了实现异步的请法)
能够看到m_downloadTimer的定义: Timer<ResourceHandleManager> m_downloadTimer;
m_downloadTimer是实现的一个定时器模块类,在它的构造函数里已经传入了回调函数的地址:ResourceHandleManager::downloadTimerCallback。
9. 一路返回到Load()函数,并返回到调用源,函数执行完毕。
10. ResourceHandleManager::downloadTimerCallback回调函数被定时器调用。
11. 能够看到downloadTimerCallback函数的代码:
调用libcurl库的接口curl_multi_fdset, curl_multi_perform等查询数据。
webkit应用场景再举例:
用户给出一个 URL (直接输入或者点击连接或者 JavaScript 解析等方式)。而后浏览器外壳调用 FrameLoader 来装载页面。 FrameLoader 首先检查一些条件 (policyCheck()) ,如 URL 是否非空、 URL 是否可达,用户是否取消等等。而后经过 DocumentLoader 启动一个 MainResourceLoader来装载页面。MainResourceLoader 调用 network 模块中的接口来下载页面内容( ResourceHandle ),实际上这里的Resourcehandle 已是平台相关的内容了,接收到数据之后,会有回调函数,告诉MainResourceLoader 数据已经接收到了。而后一路返回到 FrameLoader 开始调用HTMLTokenizer 解析 HTML 文本。解析过程当中,若是遇到 Javascript 脚本的话,也会调用 Javascript 引擎( Webkit 中的 JavascriptCore , chrome 中的 V8 )来解析。数据被解析完了之后,生成了一个一个的 node ,生成 DOM 树和 Render 树,而后经过 FrameLoaderClient 调用外部的壳把内容显示出来。”
Loader 是在WebKit 里面一个很重要的链接器,经过loader 发起IO下载网页,图片等数据,再经过loader发起解析,以及最后的渲染功能。
前面把Loader数据加载模块介绍完了,那有了数据之后就能够开始解析了,但在介绍parser模块以前,须要知道数据从curl怎么过来的,所以,本篇先介绍一下ResourceHandleManager.cpp里下面这几个函数:
headerCallback
writeCallback
readCallback
顾名思义,这三个函数是http请求发出之后的回调函数(为了实现异步操做),分别为写回调、http头回调、读回调, 你们去翻代码会发如今ResourceHandleManager的initializeHandle函数里会调用如下函数,把这几个回调注册到libcurl里:
curl_easy_setopt(d->m_handle, CURLOPT_HEADERFUNCTION, headerCallback);
curl_easy_setopt(d->m_handle, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(d->m_handle, CURLOPT_READFUNCTION, readCallback);
重点先讲一下readCallback函数,它有点特殊,它是在ResourceHandleManager的setupPOST里函数里设置的,为何呢?
下面解说以前,读者先要了解http协议的Get和Post才行,这是必须滴。由于通常请求URL地址,都只是Get请求,请求发出去后,只要接收http数据显示就好了。并不须要再发送啥数据了。
只有当Post(上传)一个文件或者提交Form表单的Post数据时,才须要读的回调, 读回调被调用的时机是: socket的connect创建成功后,先发送的是post请求的http头部数据,再发送Body的数据,也就是说readCallback是用来发送body数据的。能理解吗?
下面分别列举一下Get和Post的数据(我比较喜欢举例子,有例子更形象也容易理解):
Get
GET /books/?name=maxxiang HTTP/1.1
Host: www.qq.com
User-Agent: QQBrowser/1.3 (MTK 6235; U; MTK 1.3; ch-china; rv:1.7.6)
Connection: Keep-Alive
POST
POST / HTTP/1.1
Host: www.qq.com
User-Agent: QQBrowser/1.3 (MTK 6235; U; MTK 1.3; ch-china; rv:1.7.6)
Content-Type: application/x-www-form-urlencoded
Content-Length: 29
Connection: Keep-Alive
(此处空一行)
name=maxxiang&bookname=webkit
注意: POST的http头和Body部分,中间要有2个”\r\n”来区分。没记错的话应该是RFC 2616规定的。
下面来分别跟踪一下代码:
1) headerCallback
从上面的函数调用栈能看出,源头就是Loader章节讲到的downloadTimerCallback函数,它是读取libcurl数据的总控制函数。
Curl库的Curl_client_write函数分别把http头,按”\r\n”切隔分屡次调用headerCallback传递给Loader,每次只传一个http字段,这样的作法好处是,curl须要自已解析并保存http头部字段的数据值,同时,也方便了webkit的处理,由于webkit也是须要处理这些数据的,它须要把http头部字段都set到m_response里,后面用得着。
2) writeCallback
发现writeCallback的函数调用栈比headerCallback深了一些,是由于html body的数据是能够作压缩的,而curl发现http头部标识的是gzip压缩的数据,会先解压后,再传给webkit的writeCallback函数。
在writeCallback里,首先会判断Http的 返回Code, 200表示成功等。
if (CURLE_OK == err && httpCode >= 300 && httpCode < 400)
return totalSize;
if (d->client())
d->client()->didReceiveData(job, static_cast<char*>(ptr), totalSize, 0);
能够看到,httpCode判断了300—400之间,若是属于这个区间,就直接返回了,为何呢,
这是由于300-400之间的代码,都是须要再处理的或再跳转请求的。
举例以下(目前有用到好像只有是300-307,具体能够去查看RFC规范,这里仅举两个例子):
若是301,302都表示跳转,就不用去解析body部分了,直接跳转。
若是303表示请求是 POST,那么就要转为调用 GET再请求才能取到正确的数据。
3) readCallback
readCallback里,直接调用的m_formDataStream.read(ptr, size, nmemb)函数来上传。
FormDataStream类能够重点看一下,它的read函数实现了上传文件和上传Form表单(httpbody)的两个分支流程。
1).咱们都知道一个HTML文件,都是以<HTML>开头。(WML是以<wml>开头)在webcore中 <html>标签对应的是HTMLHtmlElement节点。
但实际上<html>标签(HTMLHtmlElement)并非DOM树的根节点,webkit中DOM树的真正根节点是:HTMLDocument。 当咱们在浏览器里打开一个空白页面的时候,webkit首先会生成DOM Tree的根节点HTMLDocument和Render Tree的根节点RenderView。也就是说,当前是空白页而没有html网页显示时,这个DOM Tree和Render Tree的根节点就已经存在了。只是这两棵Tree的子节点都是“空”的而已。理解了吗?
2).当用户在同一个浏览器页面中,前后打开两个不一样的HTML文本文件时,会发现HTMLDocument和RenderView两个根节点并无发生改变,改变的是HTMLHtmlElement及下面的子树,以及对应的Rendering Tree的子树。
为何这样设计?缘由是HTMLDocument和RenderView服从于浏览器页面的设置,譬如页面的大小和在整个屏幕中的位置等等。这些设置与页面中要显示什么的内容无关。
同时HTMLDocument包含一个HTMLTokenizer的成员,而HTMLTokenizer绑定HTMLParser。(下面具体介绍这两个组件)
这样设计的好处是: 由于HTMLElement是一开始就建立的,不会随着打开一个新的URL页面而释放(free)再建立(malloc),节省了CPU时间。
1).语言的解析通常分为词法分析(lexical analysis)和语法分析(Syntax analysis)两个阶段,WebKit中的html解析也不例外。
词法分析的任务是对输入字节流进行逐字扫描,根据构词规则识别单词和符号,分词。
2).状态机:有穷状态机是一个五元组 (Q,Σ,δ,q0,F),后面省略....
3.下面介绍Parser模块里两个“很是很是”重要的组件:
HTMLTokenizer和HTMLParser。
1).HTMLTokenizer类理解为词法解析器,HTML词法解析器的任务,就是将输入的字节流解析成一个个的标记(HTMLToken),而后由语法解析器进行下一步的分析。
2).HTMLParser类理解为语法分析器,它经过HTMLTokenizer识别出的一个一个的标识(tag)来建立Element(Node),把Element组织成一个DOM Tree, 同时,同步生成Render Tree。
4.Parser模块代码走读(如何生成DOM树和Render树的):
1). 上文提到的writeCallback接收到html数据后,首先调用ResourceLoader的didReceiveData。一路往下传:ResourceLoader::didReceiveData->MainResourceLoader::didReceiveData->..->FrameLoader::receivedData->DocumentLoader::receivedData->FrameLoader::committedLoad
2). FrameLoader::committedLoad函数这里注意,它会判断ArchiveMimeType,若是是同类的,则不须要往下走。(也就是上面第1节说的,不需往下建立HTMLTokenizer了,由于能够复用嘛)
注: 有兴趣可继续往下看代码,HTMLTokenizer是在HTMLDocument里被建立的。
3). 接下来会进入FrameLoader::addData-->write函数。 write函数首先处理字符编码的问题,把解码后的html数据,继续往下丢给HTMLTokenizer的write函数:
if (tokenizer) {
ASSERT(!tokenizer->wantsRawData());
tokenizer->write(decoded, true);
4). 真正的词法分析开始啦。
由于webkit支持边解析边绘制,也支持多线程,因此HTMLTokenizer的write函数首先会判断上次丢过来的数据是否已解析完,不然追加到上次的数据后面。
write函数里有一个大的while循环,用于逐个字符的解析,这里代码太多,只贴一下重点,我补上注释说明:
while (!src.isEmpty() && (!frame || !frame->loader()->isScheduledLocationChangePending())) {
UChar cc = *src; //从html数据Buffer中取出一个字符
bool wasSkipLF = state.skipLF(); //是否要跳过回车
if (wasSkipLF)
state.setSkipLF(false);
if (wasSkipLF && (cc == '\n'))
src.advance();
else if (state.needsSpecialWriteHandling()) {
if (state.hasEntityState())
state = parseEntity(src, dest, state, m_cBufferPos, false, state.hasTagState());
else if (state.inComment()) //注释文本,如:<!--这里是注释 -->
state = parseComment(src, state);
else if (state.inDoctype()) //HTML的DocType
state = parseDoctype(src, state);
else if (state.inServer()) //asp或jsp的服务端代码,如:<%***%>
state = parseServer(src, state);
else if (state.startTag()) { //重点注意:这里是检测到 '<'字符,
在检测到'<'字符后,表示后面跟的就是标签(html Tag)啦,这条分支里主要有两个函数:
processToken和parseToken。 这里是重点。。。。
*: processToken的做用是,在开始一个新的Tag以前,先看一下上一个tag是否已经处理完毕了?由于webkit的兼容性很是好,举例若有“<begin>”而没有“</end>”时,这里能兼容到,而不会由于网页设计人员的失误,致使网页绘制失败。(该函数还有另一个做用,下面会介绍)
*: parseTag函数,看名字就知道啦,它就是真正开始词法分析一个html tag标签的函数。
5).parseTag函数里也是一个大的while,状态机,state变量维护这个状态机,有以下几种状态:
enum TagState {
NoTag = 0,
TagName = 1,
SearchAttribute = 2,
AttributeName = 3,
SearchEqual = 4,
SearchValue = 5,
QuotedValue = 6,
Value = 7,
SearchEnd = 8
};
TagName: 一个HTML Tag的开始,它会把Tag的名字存在一个叫Token的成员变量里,它永远保存当前正在Parser的Tag的数据。
AttributeName: 在处理这个状态时,会把全部的大写转为小写。由于html标准中的attribute是不区分大小写的,这样作的目的是加快后面字符比较的速度。
SearchEqual: 循环到到'='时,会把attributeName添加到currToken这个Token里。
SearchEnd: 表示当前的Tag所有解析完了,噢,终于完整地解析完一个Tag了,这里该干吗了? 固然是生成DOM节点啦,
这个时候,token成员类变里已经存下了:Tag的名字,全部的attribute和value,有了这些后,会调用:
RefPtr<Node> n = processToken();
6). processToken就是真正生成DOM节点和Render节点的函数。
processToken函数会调用parser->parseToken(&currToken);
该函数定义:PassRefPtr<Node> HTMLParser::parseToken(Token* t)。 返回的就是一个Node的节点, Node类是全部DOM节点的父类。
7). HTMLParser::parseToken函数重点代码介绍:
RefPtr<Node> n = getNode(t); //这里返回Node节点, 往里面跟,会发现它用了不少设计模式的东东
if (!insertNode(n.get(), t->flat)) { //会调用Node* newNode = current->addChild(n); 把当前的新节点加入到DOM Tree中。
8).接下来会调用Element::attach,建立相对应的Render节点,代码以下:
void Element::attach()
{
createRendererIfNeeded();
ContainerNode::attach();
if (ElementRareData* rd = rareData()) {
if (rd->m_needsFocusAppearanceUpdateSoonAfterAttach) {
if (isFocusable() && document()->focusedNode() == this)
document()->updateFocusAppearanceSoon();
rd->m_needsFocusAppearanceUpdateSoonAfterAttach = false;
}
}
9:真正建立Render的地方:
RenderObject::createObject(), 该函数会根据不一样的type,而建立不一样的Render节点。