请选择 进入手机版 | 继续访问电脑版

芯机智

 找回密码
 立即注册

扫一扫,访问微社区

搜索
热搜: 活动 交友 discuz
查看: 7226|回复: 11

webkit开发学习笔记(全面)_第一版

[复制链接]

535

主题

625

帖子

7053

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
7053
发表于 2012-12-25 22:00:47 | 显示全部楼层 |阅读模式
由于工作需要,最近在准备一个介绍webkit的PPT文档, 我个人断断续续学习webkit的代码也有一年多了,其间也阅读了网上的一些webkit相关技术文章,但中文的资料很少,大部分都是english的,有些E文资料还需要翻墙。平常由于自已记性不好,去年看过的一些模块今年再去翻时,竟然没一点印象了,悲剧。。。
所以,借此机会,把自已对webkit的理解先做下笔记,以便于以后需要时可以方便查阅。
需要说明的是,笔记记录的有我个人的理解,也有网上摘录的片段和图片,不一定正确,也会比较凌乱,希望看到的朋友及时指正,共同进步。

Webkit的由来

十几年前的故事
1994年,Netscape浏览器曾占据整个浏览器市场的90%,风头无二(也很嚣张)。但随着微软推出win95后,把IE 1.0做为win95的插件发布,开始挑战Netscape的霸主地位,到发布IE 4.x,短短三年时间,打败Netscape。 这里面虽然说有与windows集成的原因,但从本身的功能上来讲, IE从速度和对标准的支持上来讲,已真正打败了Netscape。

此阶段的浏览器可称为第一代浏览器。它的主要特点是单窗口型式。竞争的最主要是访问速度、兼容性。原因:90年代都大多是用modem拨号上网,56K/S。

2.Webkit出生
Apple公司在它的Mac OS X里,集成了基于KHTML 改进型的 WebKit 引擎的浏览器,命名为:Safari,当年苹果比较了 Gecko 和 KHTML 后,之所以选择了后者,就因为它拥有清晰的源码结构、极快的渲染速度。(KHTML是由KDE 小组开发的)
随后, apple将它开源。

至此,第二代浏览器,基本上是三分天下:
  Trident: IE系列, 以Trident 作为内核引擎;
  Gecko: Firefox 是基于 Gecko 开发;
WebKit: Safari, Google Chrome, 搜狗双核浏览器(集成IE和chrome), QQ浏览器5。

WebKit 内核在手机上的应用也十分广泛,例如 Google 的手机 Gphone、 Apple 的 iPhone, Nokia’s Series 60 browser 等所使用的 Browser 内核引擎,都是基于 WebKit。

总结:
webkit是什么?
答:Webkit是一套浏览器排版代码, 已开源,主要由apple公司在维护。强调: webkit仅仅是一套排版引擎,
举个例子说明下:
google的chrome是一个浏览器对吧, 那chrome主要包含以下模块: 外壳UI(多标签,菜单,状态栏,网址输入栏等),读取网络数据的模块,排版解析模块,JS解析引擎。
外壳UI是google自已写的,js引擎是google写的V8, 读取网络数据模块用的winhttp,只有排版引擎用的webkit。
不知道我说清楚了没,呵呵。

WebKit is an open source Web content engine for browsers and other applications. We value real-world web compatibility, standards compliance, stability, performance, security, portability, usability, and relative ease of understanding and modifying the code (hackability).


Webkit编译环境
Webkit的官网:http://www.webkit.org/
说明:下面的几种编译方法,越往下面看越简单噢。

Webkit提供以下几种主要的编译环境
1). Max os X(Xcode Tools package)
2). Windows (Visual Studio)                最为常用,毕竟用winxp系统的还是多数
3).QT
4).GTK
具体的环境搭建见:http://www.webkit.org/building/tools.html

WebKit winxp下编译小结
网上很多人抱怨webkit总是编译不过,确实, webkit没有提供一个下载代码后,直接可以用VS工具编译的方法, 因为它用到了gcc编译环境,在windows下编译的话,需要安装cygwin才行,由于它的编译脚本用的perl,又需要安装perl的环境。总之比较麻烦。
   这也是因为网上的文章,有介绍QT安装环境的,有的是介绍官方webkit的安装环境的,还有的是介绍safari的, 所以在网上查阅文档时一定要找到你相对应的才行。
重点要注意的地方:
下载Cygwin,一定要下载webkit提供的版本。
用vs2005的话,一定要打SP1.
QuickTime SDK和directX SDK必不可少。
4.启动Cygwin ,安装官网方法,执行./WebkitTools/Scripts/build-webkit脚本。
较简单的编译安装QT webkit
直接在QT的官网下载如:qt-sdk-win-opensource-2009.04.exe(该版本是我09年下半年使用的,官网上应该早有新版本了)
不需要cygwin, 不需要quickTime,只需要执行下面几个步骤:
1、下载安装Visual.Studio.net.2005,或2008;
2、将qt-sdk-win-opensource-2009.04.exe解压到某个目录下;以D:\qt4.5.0为例
3、配置环境变量:
在“我的电脑”—“属性”---“高级”---环境变量---**的用户变量---新建,
添加一个变量名为QTDIR 变量值为D:\qt4.5.0\qt-win-opensource-src-4.5.0
PATH变量值为D:\qt4.5.0\qt-win-opensource-src-4.5.0\bin
添加新的环境变量,名字为"QMAKESPEC",值为"win32-msvc2005".
4、编译源代码
通过"开始"菜单 -> "Microsoft Visual Studio 2005" -> "Visual Studio Tools", 运行命令行.

...

到qt源码的目录下,运行命令:
>configure.exe -webkit
编译过程中,会问一个问题:

是否接受GPL协议,选'y'.

经过一段时间以后,qmake被编译出来了.

接着编译一大堆代码,简单的敲入nmake即可.

5、编译应用程序
>qmake
>nmake
就会在该应用程序下,就会在debug目录下生成可以执行文件。

不过我个人不太喜欢QT,只是安装了跑一跑,QT的安装环境包含了webkit的源代码, 同时它自带了一个简单的Browser项目(如下):

编译运行。在Visual Studio中设置browser工程为主工程,然后编译。可以顺利编译完成,下面是运行后的效果图。


最最简单的webkit学习环境-ISee
Isee是一位中国人移植的webkit, 在winxp下用vs2008直接编译即可调试,用于学习最好,强烈支持,也是一位同事推荐给我的,后面的代码走读主要基于该环境。
Isee还可以直接移植到wince平台运行噢。

官网:http://code.google.com/p/iseebrowser/
备注:原作者已经不再维护了。所以webkit内核的版本号有点老。

webkit在vs2008中编译
见 : http://xjchilli.blog.163.com/blo ... 920091016115533158/


Webkit整体介绍
Webkit的结构图(以ISee架构举例):




cairo         一个2D绘图库          
casqt         Unicode处理用的库,从QT中抽取部分代码形成的          
expat        一个XML SAX解析器的库          
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库。用于解压缩。         


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.)

WebKit分模块介绍(这里简单列出,后面再具体介绍)
Webkit平台相关
CURL网络库
libPng, LibJpeg图形处理相关
sqlite小型关系数据库
WebCore核心
Loader加载资源及Cache 实现(Curl)
DOM : HTML词法分析与语法分析
DOM : DOM节点与Render节点创建,形成DOM树
Render:Render树介绍,RenderBox
Layout:排版介绍
Css Parser模块
Binding-DOM与JavascriptCore绑定的功能
JavascriptCore-javascript引擎
API-基本javascript功能
Binding与其它功能绑定的功能,如OM,C,JNI
DerviedSource自动产生的代码
PCRE-Perl-Compatible Regular Expressions  
KJS-Javascript Kernel


4. 页面的整个处理流程—(简单介绍,详细流程在后面笔记中)

用户输入网址后,FrameLoader::load函数会接收到URL。
把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出来












Webkit-libCurl库介绍
前面有说道webkit仅仅是一个页面排版的引擎,所以,对webkit来说,网页数据(html文件,图片,.css,.js文件)的请求与接收都是通过第三方的库:libCurl来处理。
打开webkit开发工程(.sln)即可以看到,libcurl可以被静态或动态链接到主工程中。
Libcurl就是指的curl,只是在webkit工程中,不作为单独的进程存在,而是被编译成动态库。
webkit主要用到curl的以下功能:
Http协议。包含:Get, put, Post, Cookie管理。
https协议。
本地文件缓存。(前进,后退管理)

Webkit具体调用了哪些curl接口,详见后面Loader模块介绍章节。这里简单列举:
curl_global_init(CURL_GLOBAL_ALL);
curl_multi_init()
curl_share_init()
curl_share_setopt()
curl_easy_getinfo()
curl_multi_fdset()
curl_multi_perform()
curl_multi_info_read()
curl_multi_cleanup()
curl_share_cleanup()
curl_global_cleanup();       

可以看到,由于webkit要支持同时请求多个http数据,所以用到的是curl的multi接口。

在介绍Loader之前,先介绍一下libcurl,打下基础。

以下附一篇libcurl的介绍:


一、     概念
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”,这样就可以正常发送了。
二、              例程
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












Webkit 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与Frame(严格说是FrameView)是一一对应的,Frame关注UI,Page关注数据。现在的浏览器一般都提供同时打开多个窗口,每一个窗口对应的数据就是这个Page在管理了。
  在 page.cpp 文件里,还有个重要的全局指针变量: static HashSet<age*>* 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等。后面可以详细讲讲。

上面介绍的概念比较多,我也不晓得好不好理解,没理解也不怕,多去看看代码,这是必须的。

另外看看下面的图,就会清晰很多的。




二、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] example: 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函数进入"olicy"状态。

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等查询数据。

以下调试时Loader执行流程的函数调用栈截图:




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的功能:
Loader 是在WebKit 里面一个很重要的连接器,通过loader 发起IO下载网页,图片等数据,再通过loader发起解析,以及最后的渲染功能。

Loader的代码和架构清楚了没, 没清楚? 看代码吧,呵呵。文笔不好,或者加我QQ一起交流:270977395




Webkit Parser模块前篇

在讲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&publisher=china
注意: POST的http头和Body部分,中间要有2个”\r\n”来区分。


下面来分别跟踪一下代码:
headerCallback

从上面的函数调用栈能看出,源头就是Loader章节讲到的downloadTimerCallback函数,它是读取libcurl数据的总控制函数。
Curl库的Curl_client_write函数分别把http头,按”\r\n”切隔分多次调用headerCallback传递给Loader,每次只传一个http字段,这样的做法好处是,curl需要自已解析并保存http头部字段的数据值,同时,也方便了webkit的处理,因为webkit也是需要处理这些数据的,它需要把http头部字段都set到m_response里,后面用得着。

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再请求才能取到正确的数据。

readCallback
readCallback里,直接调用的m_formDataStream.read(ptr, size, nmemb)函数来上传。
FormDataStream类可以重点看一下,它的read函数实现了上传文件和上传Form表单(httpbody)的两个分支流程。


Webkit Parser模块

1.在介绍parser模块前,先介绍另一个基础,这个基础是理解Parser和DOM的核心点:

    1).我们都知道一个HTML文件,都是以<HTML>开头。(WML是以<card>开头)在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时间。


2.回顾一下大学课程里的《编译原理》课:
    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节点。



Parser这块代码挺多的,上面只记录了重点。
从上面函数栈中,找到相应的函数,设置断点去调试吧, 代码跟踪一遍后就清楚了。


Webkit layout模块




Webkit JS引擎



Webkit 插件加载

RenderView:
    void addWidget(RenderObject*);
void removeWidget(RenderObject*);
void updateWidgetPositions();

    typedef HashSet<RenderObject*> RenderObjectSet;
RenderObjectSet m_widgets;

插件加载时调用的源头:
HTMLPlugInElement::updateWidgetCallback

插件来源判断加载重点函数:
RenderPartObject::updateWidget

真正加载插件的入口:
WebCore:luginPackage::load函数



官方Webkit最新代码(r86499)在win7下编译时的注意事项:

最近在blog上写下关于webkit的笔记后,经常有朋友会问我, 在安装webkit.org官方的开发环境时,老是编译不过, 刚好最近我换了个新本本, 需要在新本本上也装上webkit的开发环境,我下载的是r86499的版本(最新),  故在此把需要注意的事项在这里总结下:

http://www.webkit.org/building/tools.html
官方已经给出了安装步骤,那这里就不再重复. 但官方给出的太粗, 我把有可能出错的地方记录下来了,大概是以下几点:
环境变量
在系统环境变量中加入:
WEBKITOUTPUTDIR = %WEBKIT_HOME%\WebKitBuild   /*注意老的版本该目录是Build,名称不同,如果照网上的文章,就会出错*/
WEBKITLIBRARIESDIR = %WEBKIT_HOME%\WebKitLibraries\win

WEBKIT_HOME = E:\webkit\Webkit\WebKit-r86499  /*这里指你webkit源代码的路径, 不要照抄我的*/
VS2005最好安装在C盘, 如果安装在别的盘, 需要加一个宏,webkit 的编译脚本才能找到正确的位置(不在C盘时,才加该宏):
VSINSTALLDIR = D:\Program Files\Microsoft Visual Studio 8
不想加宏的话, 找到Tools\Scripts\webkitdirs.pm, 这个文件里的setupCygwinEnv函数中的$programFilesPath变量,就是去识别路径的. 直接改也行.

打开webkit.sln编译过程中,经常报某个文件的某句代码error, 解决方法有下面两种情况:
用VS2005打开G:\Webkitsrc\WebKit\win\WebKit.vcproj\WebKit.sln文件,将各个工程属性中C++设置项中的将警告视作错误的选项值改为否
直接找到该文件, 用notepad打开,保存成utf-8就行.


还有一点要注意的是, 网上写的开发环境安装,大多是基于老是webkit目录和代码写的, 在配置过程中 , 要注意采用最新的目录名. 比如: WebKitLibraries,以前不叫这个名字的.

主要就这几点, 整个编译过程,大概1小时左右,看机器性能啦.

在写的时候是基于2万多的代码, 现在官方最新已经9万多,代码的实现变化挺大的, 因为决定暂停,重新基于9万号的代码来写.

我的QQ: 270977385(maxxiang), 对webkit有兴趣的朋友, 可以一起交流

在终极的分析中,一切知识都是历史;在抽象的意义下,一切科学都是数学;在理性的基础上,所有的判断都是统计。——C.R. Rao
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|芯机智 ( 京ICP备18048803号 )

GMT+8, 2020-12-1 19:07 , Processed in 0.507408 second(s), 21 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表