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節點。