• webkit模块介绍


    一、Webkit模块

     

    用到的第三方库如下:

     

    cairo

    一个2D绘图库

    casqt

    Unicode处理用的库,从QT中抽取部分代码形成的

    expat

    一个XML SAX解析器的库

    freetype

    矢量字库接口库,用于存取ttf矢量字体文件

    libcurl

    一个开源的url库,支持HTTPFTP等协议

    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:移植层,主要包含: GUIFile System, ThreadText,图片编解码等与平台相关的函数。

    JavaScriptCoreJS虚拟机,相对独立,主要用于操作DOM DOMW3C定义的规范,主要用于定义外部可以操作的浏览器内核的接口,而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)       RenderRender树介绍,RenderBox

    5)       Layout:排版介绍

    6)       Css Parser模块

    7)       Binding-DOMJavascriptCore绑定的功能

    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出来

    二、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数据,所以用到的是curlmulti接口。

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

    以下附一篇libcurl的介绍:

    一、     概念

    1.         为什么要使用libcurl

    1)        作为http的客户端,可以直接用socket连接服务器,然后对到的数据进行http解析,但要分析协议头,实现代理…这样太麻烦了。

    2)        libcurl是一个开源的客户端url传输库,支持FTPFTPSTFTPHTTPHTTPSGOPHERTELNETDICTFILELDAP,支持WindowsUnixLinux等平台,简单易用,且库文件占用空间不到200K

    2.         getpost方式

    客户端在http连接时向服务提交数据的方式分为getpost两种

    1)        Get方式将所要传输的数据附在网址后面,然后一起送达服务器,它的优点是效率比较高;缺点是安全性差、数据不超过1024个字符、必须是7位的ASCII编码;查询时经常用此方法。

    2)        Post通过Http post处理发送数据,它的优点是安全性较强、支持数据量大、支持字符多;缺点是效率相对低;编辑修改时多使用此方法。

    3.         cookiesession

    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)        httphttps的区别

    1)        Http是明文发送,任何人都可以拦截并读取内容

    2)        Https是加密传输协议,用它传输的内容都是加密过的,httpshttp的扩展,其安全基础是SSL协议

    c)        base64编码

    1)        为什么要使用base64编码

    如果要传一段包含特殊字符比较多的数据,直接上传就需要处理转意符之类的很多问题,用base64编码,它可以把数据转成可读的字串,base64a-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

    三、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关注UIPage关注数据与接口。现在的浏览器一般都提供同时打开多个窗口,每一个窗口对应的数据就是这个Page在管理了。

       page.cpp 文件里,还有个重要的全局指针变量: static HashSet<Page*>* allPages; 这个变量包含了所有的page 实例。

    3)FrameView可以理解为为一个网页的ViewPort, 它提供一个显示区域,同时包含的有Render根节点、layout排版相关接口、Scroll相关等。FrameViewLayout排版的总入口。

    4)RenderViewFrameView差不多,只是分工不同,它管理与Render树相关的东东。

    5) FrameLoader:重点,FrameLoader类将Documents加载到Frames。当点击一个链接时,FrameLoader创建一个新的处于“policy”状态的DocumentLoader对象,一旦webkit指示FrameLoader将本次加载视为一个导航(navigation)FrameLoader就推动DocumentLoader进入“provisional”状态,(在该状态,DocumentLoader发调用CURL发起一个网络请求,并等待是html还是下载文件。)同时, DocumentLoader会创建一个MainResourceLoader对象(该对象在后面单独介绍)。

    6)。DOMWindow: 实现了Dom的一些接口,如CreateNode等。后面可以详细讲讲。

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

    二、WebkitLoader有两条加载数据的主线

    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的一个类)setHTTPHeaderFieldsetHTTPContentType等与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自已添加到ResourceHandleManagerm_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头回调、读回调, 大家去翻代码会发现在ResourceHandleManagerinitializeHandle函数里会调用以下函数,把这几个回调注册到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函数,它有点特殊,它是在ResourceHandleManagersetupPOST里函数里设置的,为什么呢?

    下面解说之前,读者先要了解http协议的GetPost才行,这是必须滴。因为一般请求URL地址,都只是Get请求,请求发出去后,只要接收http数据显示就行了。并不需要再发送啥数据了。

    只有当Post(上传)一个文件或者提交Form表单的Post数据时,才需要读的回调, 读回调被调用的时机是: socketconnect建立成功后,先发送的是post请求的http头部数据,再发送Body的数据,也就是说readCallback是用来发送body数据的。能理解吗?

    下面分别列举一下GetPost的数据(我比较喜欢举例子,有例子更形象也容易理解):

    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

    注意: POSThttp头和Body部分,中间要有2” ”来区分。没记错的话应该是RFC 2616规定的。

     

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

    1)       headerCallback

    从上面的函数调用栈能看出,源头就是Loader章节讲到的downloadTimerCallback函数,它是读取libcurl数据的总控制函数。

    Curl库的Curl_client_write函数分别把http头,按” ”切隔分多次调用headerCallback传递给Loader,每次只传一个http字段,这样的做法好处是,curl需要自已解析并保存http头部字段的数据值,同时,也方便了webkit的处理,因为webkit也是需要处理这些数据的,它需要把http头部字段都setm_response里,后面用得着。

    2)       writeCallback

    发现writeCallback的函数调用栈比headerCallback深了一些,是因为html body的数据是可以做压缩的,而curl发现http头部标识的是gzip压缩的数据,会先解压后,再传给webkitwriteCallback函数。

             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树的根节点,webkitDOM树的真正根节点是:HTMLDocument 当我们在浏览器里打开一个空白页面的时候,webkit首先会生成DOM Tree的根节点HTMLDocumentRender Tree的根节点RenderView。也就是说,当前是空白页而没有html网页显示时,这个DOM TreeRender Tree的根节点就已经存在了。只是这两棵Tree的子节点都是“空”的而已。理解了吗?

        2).当用户在同一个浏览器页面中,先后打开两个不同的HTML文本文件时,会发现HTMLDocumentRenderView两个根节点并没有发生改变,改变的是HTMLHtmlElement及下面的子树,以及对应的Rendering Tree的子树。

             为什么这样设计?原因是HTMLDocumentRenderView服从于浏览器页面的设置,譬如页面的大小和在整个屏幕中的位置等等。这些设置与页面中要显示什么的内容无关。

    同时HTMLDocument包含一个HTMLTokenizer的成员,而HTMLTokenizer绑定HTMLParser(下面具体介绍这两个组件)

             这样设计的好处是: 因为HTMLElement是一开始就创建的,不会随着打开一个新的URL页面而释放(free)再创建(malloc),节省了CPU时间。

        

        1).语言的解析一般分为词法分析(lexical analysis)和语法分析(Syntax analysis)两个阶段,WebKit中的html解析也不例外。

        词法分析的任务是对输入字节流进行逐字扫描,根据构词规则识别单词和符号,分词。

        2).状态机:有穷状态机是一个五元组 (Q,Σ,δ,q0F),后面省略....

     

    3.下面介绍Parser模块里两个“非常非常”重要的组件:

        HTMLTokenizerHTMLParser

        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 == ' '))

                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节点。

  • 相关阅读:
    Android中fragment之间和Activity的传值、切换
    IOS后台运行浅析
    IOS7 Background Fetch后台应用程序刷新
    IOS 7四种后台机制
    注册苹果开发者账号的详细步骤
    iOS关闭键盘的两种简单方法
    iOS5 切换中文键盘时覆盖输入框的完美解决方案
    IOS开发之NSLog使用技巧
    IOS总结 静变量static、全局变量extern、局部变量、实例变量
    高德地图初始化 ios
  • 原文地址:https://www.cnblogs.com/bigben0123/p/3154890.html
Copyright © 2020-2023  润新知