文章引用地址:http://blog.csdn.net/dlmu2001/article/details/6164873
目錄:
摘要:在分析內核的時候,Frame是首當其沖的一個類,本文將分析Frame類的代碼。
摘要:FrameLoader類負責一個Frame的加載,在Frame的流程中起到非常重要的重要,同很多組件都有交互,本文將分析FrameLoader類的代碼。
摘要:Page類就是用來對應這樣的頁面請求。Page類是WebKit中非常重要的一個類,它就像內核對外的一個聚合器。
摘要:本文介紹 WebCore 中 Loader 模塊是如何加載資源的,分主資源和派生資源分析 loader 模塊的類關系。
摘要:本文分析WebKit中html的解析過程,DOM節點樹的建立。
摘要:本系列通過分析WebKit的源代碼,試圖分析WebKit的內核設計架構,模塊之間的關系,分析的時候以Qt的移植為參考,涉及移植的東西不多,主要還是以內核為主。在分析內核的時候,Frame是首當其沖的一個類,本文將分析Frame類的代碼。
1. 描述
Frame類是WebCore內核同應用之間聯系的一個重要的類。它有點像設計模式中的Façade,將內核的各個不同的零配件組裝在了一起,但又不是Façade,因為用戶很多時候還是要直接去操作里面的組件。除了設計上的考慮,Frame還有語法上的意義,它對應於Page里面的幀。
2. 類結構
1) FrameTree對象用來協助管理父幀和子幀的關系,常見的比如main frame之中有iframe元素,就會調用FrameLoaderClientQt::createFrame來產生子幀,產生的子幀會通過appendChild添加到主幀的樹狀結構中。Frame通過FrameTree對象,可以方便地訪問它的父幀,子幀,兄弟幀。
2) 維護FrameLoader對象用來完成frame的加載,FrameLoader是一個非常重要的類,后續進行進一步的分析。
3) 維護NavigationScheduler對象用來管理頁面跳轉調度(比如重定向,meta refresh等)。
4) DOMWindow用來管理同DOM相關的事件、屬性和消息。
5) FrameViwe類用於Frame的排版。
6) Frame文檔解析后,對每一個tag或者attr,會有對應的dom節點關聯,Document類用來管理這些dom節點。不同的文檔類型繼承出不同的子類,比如HTML文檔對應子類HTMLDocument,XML文檔對應於XMLDocument。
7) SciptController對象,腳本控制器,用來管理腳本的執行和操作。
8) Editor對象用來處理頁面的編輯相關的操作,比如拷貝,粘貼,輸入等,Editor對象,它同Page類的EditorClient對象緊密合作。和EditorClient的關系就如同Page同Frame的關系。
9) SelectionController用來管理Frame中的選取操作。
10) AnimationControlle,動畫控制,控制動畫的播放,暫停,繼續(同HTML video標簽是否有關系?)
11) EventHandler,事件處理對象,這里的對象主要是同上層應用也就是用戶參與的事件,比如鼠標事件,按鍵事件(快捷鍵等),滾動事件,resize事件等。這是一個瀏覽器外殼經常需要打交道的類。
3. 主要接口
3.1 Create
static PassRefPtr<Frame> create(Page*,HTMLFrameOwnerElement*,FrameLoaderClient*)
描述: 調用Frame構造函數,創建出Frame對象。有兩個地方會創建Frame對象,一是要加載一個新的頁面請求,這個時候會創建main frame,一是在加載子幀的時候,通過FrameLoaderClientQt的createFrame接口,創建子幀對應的Frame對象,在第一種情況中,HTMLFrameOwnerElement參數為NULL,第二種情況傳子幀的父元素。在一個tab頁內,main frame會重用。
調用系列:
->QwebPage::setView
->QwebPage::setViewportSize
->QwebPage::mainFrame
->QwebPagePrivate::createMainFrame
->QwebFrameData::QwebFrameData
->Frame::create
->FrameLoader::finishedLoading
->……
->HTMLDocumentParser::append
->……
->HTMLTreeBuilder::processToken
->……
->HTMLElementBase::openURL
->SubFrameLoader::requestFrame
->……
->FrameLoaderClientQt::creatFrame
->QwebFrameData::QwebFrameData
->Frame::create
3.2 createView
void createView(const IntSize&, const Color&, bool, const IntSize&, bool,ScrollbarMode = ScrollbarAuto, bool horizontalLock = false,ScrollbarMode = ScrollbarAuto, bool verticalLock = false)
描述:創建出FrameView對象,以用於之后的排版。應用調用這個函數的時候需要傳入同排版有關的一些信息,如初始視窗大小,背景色,滾動條模式等。創建出FrameView以后,即調用Frame::setView設置成當前的FrameView。
函數調用系列:
->FrameLoader::commitProvisionalLoad
->FrameLoader::transitionToCommitted
->FrameLoaderClientQt::transitionToCommittedForNewPage
->Frame::createView
3.3 setDocument
void setDocument(PassRefPtr<Document>)
描述:設置同Frame關聯的Document對象(一般是DocumentWriter創建的)。
函數調用系列:
->QWebFrame::QwebFrame
->QwebFramePrivate::init
->Frame::init
->FrameLoader::init
->DocumentWriter::begin
->Frame::setDocument
->DocumentLoader::receivedData
->DocumentLoader::commitLoad
->FrameLoaderClientQt::committedLoad
->DocumentLoader::commitData
->DocumentWriter::setEncoding
->DocumentWriter::willSetEncoding
->FrameLoader::receivedFirstData
->DocumentWriter::begin
->FrameLoader::clear
->Frame::setDocument
3.4 init
void Frame::init()
描述:Frame對象初始化,會調用FrameLoader::init初始化FrameLoader對象。
調用系列:
->QWebFrame::QWebFrame
->QwebFramePrivate::init
->Frame::init
3.5 setPageAndTextZoomFactors
void setPageAndTextZoomFactors(float pageZoomFactor, float textZoomFactor)
描述:設置頁面放大因子和文字放大因子。在網頁縮放或者改變網頁字體大小的時候調用。
摘要:本系列通過分析WebKit的源代碼,試圖分析WebKit的內核設計架構,模塊之間的關系,分析的時候以Qt的移植為參考,涉及移植的東西不多,主要還是以內核為主。FrameLoader類負責一個Frame的加載,在Frame的流程中起到非常重要的重要,同很多組件都有交互,本文將分析FrameLoader類的代碼。
1. 概述
顧名思義,FrameLoader是一個Frame的loader,它的作用就是為客戶提供一個下載一個Frame的一系列接口。這里的客戶指的是類的客戶,比如Frame類,間接客戶是上層應用,比如qwebframe。
從它的定義看,最容易想到的是一個load接口,用來將一個frame load下來。任何一個頁面至少都需要一個mainframe,因此一個頁面的下載一般就是從load一個mainframe開始。
在load frame的過程中,通過FrameLoaderClient接口將load過程的不同階段告知客戶。
FrameLoader通過setDocumentLoader相當於把load的工作委托給了DocumentLoader類。
FrameLoader同DocumentLoader是has-a的關系。一般在load的時候創建DocumentLoader。Frame調用DocumentLoader的startLoadingMainResource開始load frame。
2. 類關系
1)Frame和FrameLoader是contain-a的關系,在Frame的構造函數中調用FrameLoader的構造函數,調用時傳入參數Frame指針和FrameLoaderClient指針。
2)Frame有可能有子Frame,所以維護SubFrameLoader對象m_subframeLoader來管理子Frame的load。Frame可以對應xml document,也可對應html document,等等。跟Document相關的子resource的load不在FrameLoader的職責范圍內。
3)包含一個DocumentWriter類對象m_writer,當Frame的數據load finish的時候,將數據傳給DocumentWriter類,進行下一步的處理(比如解碼)
4)FrameLoader維護了三個DocumentLoader對象,分別對應於不同的階段,m_policyDocumentLoader對應於收到用戶load調用,進行policy check階段,m_provisionalDocumentLoader對應於policy check通過以后,Frame數據還沒有到來之前,它會負責startLoadingMainResource的調用。m_documentLoader則是Frame第一個數據到來以后使用的DocumentLoader,這個時候,前一個主Frame的DocumentLoader已經不能再用(user agent開始白屏,刷掉前一個頁面的顯示)。
5)包含一個HistoryController對象,用於操作歷史記錄相關的接口,保存或者恢復Document和View相關的狀態,維護前進后退隊列,以實現前進后退功能,前進后退本質上是同Page對象關聯的,FrameLoader通過HistoryController操作m_backFowardController對象
6)包含一個ResourceLoadNotifier對象,主要用於同ResourceLoader及FrameLoaderClient打交道,可以理解為ResourceLoader有事件變化或者發生的時候,通知FrameLoader的一個手段
7)包含一個SubframeLoader對象,當FrameLoader下載的Document有子幀需要請求的時候(比如HTMLDocument中解析到iframe 元素),用來處理子幀請求
8)將FrameLoader的狀態封裝到FrameLoaderStateMachine中,這個狀態同FrameState不同,FrameState傾向於判斷Frame涉及的Document的下載狀態,是出於發起狀態(Provisional),還是出於已經收到響應但不全(CommittedPage),還是響應收全的狀態,傾向於同http相關。而FramLoaderStateMachine傾向於同DocumentLoader相關,用來描述FrameLoader處理DocumentLoader的節點,是處於已經創建,還是顯示的狀態。
9)PolicyChecker主要用來對FrameLoader進行一些校驗。包括三種校驗:NewWindow,Navigation和Content。NewWindow對應於瀏覽器需要新開一個tab頁或窗口的時候,Navigation對應於一個頁面請求發起的時候,Content校驗對應於收到數據以后(判斷Mime type等),PolicyChecker通過提供對應的接口,由FrameLoaderClient來對這些請求進行校驗,以確定是否允許繼續,或者需要其它的動作。
3. 主要接口
Frame::init()
功能:FrameLoader的初始化
函數調用系列
->QWebFrame::QWebFrame(QwebPage* parent,QWebFrameData *frameData)
->QWebFramePrivate::init(QWebFrame* qwebframe,QWebFrameData* frameData)
->Frame::init()
->FrameLoader::init()
說明:主要做一些自身的初始化工作,比如初始化狀態機,Sandbox Flags,創建DocumentLoader被設置為Policy DocumentLoader和Provisional DocumentLoader,調用DocumentLoader和documentWriter等的接口進行初始化操作
FrameLoader::commitProvisionalLoad()
功能:提交Provisional階段下載的數據
函數調用系列:
->DocumentLoader::finishLoading
->DocumentLoader::commitIfReady
->FrameLoader::commitProvisionalLoad
或者
->ResourceLoader::didReceiveData
->MainResourceLoader::addData
->DocumentLoader::receiveData
->DocumentLoader::commitLoad
->DocumentLoader::commitIfReady
->DocumentLoader::commitProvisionalLoad
說明:這個接口主要的操作是將Provisional DocumentLoader設置成DocumentLoader,因為已經收到數據,所以FrameState也會躍遷到FrameStateCommittedPage。還有歷史記錄,PageCache相關的操作。另外,這個接口會間接調用FrameLoaderClientQt::transitionToCommittedForNewPage,通過Frame::createView創建出FrameView來。
Frame::finishedLoading()
功能:frame請求網絡加載完成的時候調用此接口
函數調用系列
->ResourceLoader::didFinishLoading
->MainResourceLoader::didFinishLoading
->FrameLoader::finishedLoading
->FrameLoader::init()
說明:檢查是否有網絡錯誤,告訴DocumentLoader和DocumentWriter下載完成,以便進行后續操作(提交數據,解析)。
FrameLoader::finishedParsing()
功能:解析完成調用此接口
函數調用系列
->DocumentWritter::end
->….
->Document::finishParsing
->….
->Document::finishedParsing
->FrameLoader::finishedParsing
FrameLoader::load(const ResourceRequest& request,bool lockHistory)
功能:加載一個frame請求,Frame請求相關的數據,封裝成ResourceRequest傳入。
函數調用系列:一般由應用觸發調用
說明:這個接口調用FrameLoaderClientQt::createDocumentLoader創建出DocumentLoader,並以此DocumentLoader為Policy Document Loader,進入Policy check流程。
摘要:瀏覽器的請求一般是以頁面請求為單位,當用戶通過網址欄輸入一個url,瀏覽器就開始一個頁面請求。而一個頁面請求可能包含有一到多個頁面子幀,以及圖片、CSS和插件等派生子資源。Page類就是用來對應這樣的頁面請求。Page類是WebKit中非常重要的一個類,它就像內核對外的一個聚合器。
關鍵詞:WebKit內核源代碼,WebCore,Page,Frame,WebKit架構
1. 概述
瀏覽器的請求一般是以頁面請求為單位,當用戶通過網址欄輸入一個url,瀏覽器就開始一個頁面請求。而一個頁面請求可能包含有一到多個頁面子幀,以及圖片、CSS和插件等派生子資源。Page類就是用來對應這樣的頁面請求。前進后退,導航,編輯,右鍵菜單,設置,Inspector等這些用戶參與的動作,大部分是同Page相關的。而標記語言解析、排版、加載則更多地同Frame相關。
我們通過幾個圖來看下Qt移植中Page類同應用之間的關系。
QWebPage通過QWebPagePrivate維護Page類的指針,並在QWebPagePrivate的構造函數中實例化Page對象。QWebPage類通過之后的createMainFrame調用實例化QwebFrame,而在QwebFrameData的構造函數中,以Page指針為參數調用了Frame::create 創建出Frame對象。
Page類通過組合其它類的方式,實現了很多功能,Page類本身並沒有多少代碼。
2. 類關系
2.1 PageGroup
PageGroup並不是用來對Page進行管理的,而是設計用來將一些具有共同的屬性或者設置的Page編成組的,以方便對這些屬性進行管理。目前這樣的屬性包括localStorage的屬性,IndexDB,User Script,User StyleSheet等。最常見的同PageGroup相關的操作是維護已訪問鏈接(如addVisitedLink等接口)。根據地瓜的理解,假設WebKit內核之上架設多個應用(瀏覽器是一個應用),比較可能的是,一個應用獨立一個PageGroup。這里同多tab頁沒有關系,多tab頁屬於同一個PageGroup。地瓜曾在mailing group上就這個問題咨詢過,一位RIM的同學給我舉了一個例子,比如一個基於WebKit的郵件程序,一方面他可能調用基於webkit的browser來顯示網頁,另外他本身也基於webkit來顯示一些郵件,這兩個之間的setting有很大可能不一樣,他們就使用不同的PageGroup。
PageGroup中有這個Group已經安裝並且使用的User Script和User StyleSheet的集合,一般在網頁解析完畢后,這些User Script和User StyleSheet會插入到Document中。
PageGroup中還維護了Local Storage和Index DB相關的設置,比如它們的Path,上限等,通過GroupSettings類實現。
PageGroup創建以后,每次創建一個新的Page對象,會通過addPage接口加入到這個PageGroup的m_pages中。
每次有導航行為發生的時候,會調用addVisitedLink來將url加入到已訪問鏈接中。如果瀏覽器要跟蹤已訪問的接口,則在初始化的時候必須調用PageGroup::setShouldTrackVisitedLinks,且參數為true。此處shouldTrackVisitedLinks是一個靜態的全局變量,也就是說,所有應用維護一樣的行為(一個應用將其設置為false會影響到其它同樣基於此核的應用)?
Page類中維護了PageGroup的指針,並提供了group接口,這是個lazy接口,如果m_group不存在,會調用InitGroup來創建一個。對於Page類來說,如果沒有設置GroupName,則在初始化的時候會生成一個空GroupName的PageGroup,由m_singlePageGroup維護,並把指針賦給m_group,如果以非空的名字調用了setGroupName,則會重新創建PageGroup,此時這個PageGroup由m_group來維護。
2.2 Setting
WebCore中的設置相關的類,瀏覽器應用的不少配置、選項同該類相關,Qt移植中,應用在創建Page對象后,會根據Page::settings來實例化QwebSetting。
2.3 Chrome
原生窗口接口類,參考地瓜寫的”WebKit中的Chrome和ChromeClient”。
2.4 其它
SelectionController:負責管理Page中的選取操作,絕大部分選取操作是基於Frame的,只在Frame的Selection為空的時候,對焦點游標的繪制工作才會使用到Page類的SelectionController。
SharedGraphicsContext3D:共享3D圖形上下文,為了優化2D顯示而加入。在加速的2D canvas中,引入的DrawingBuffer的概念,SharedGraphicsContext3D提供了createDrawingBuffer來創建DrawingBuffer。
DragController:拖拽控制器。Chrome的超級拖拽功能同這個有關?地瓜會在以后對此進行求證。
FocusController:焦點控制器。考慮到焦點會在各個frame之間切換,所以由Page類維護焦點控制器最合適不過。
ContextMenuController:右鍵下拉菜單控制器。
InspectorController:Inspector控制器,瀏覽器中的很多開發工具都同這個類相關。
GeolocationController:定位定位服務控制器。
DeviceMotionController:設備移動控制器
DeviceOrientationController:設備方向控制器
SpeechInputClient:語音輸入Client。
ProgressTracker:進度跟蹤。
BackForwardController:前進后退操作控制。
Frame:一個Page由至少一個主幀和若干個其它子幀構成。
HistoryItem:歷史記錄。
PluginData:插件相關,未來可能同PluginDatabase類合並。主要是初始化Plugin的信息。
PluginHalter:用來控制Plugin的停止和重新開始。
RenderTheme:這個類提供了控件的渲染和繪制接口。Qt移植中,RenderThemeQt是RenderTheme接口的具體實現。
EditorClient:同編輯功能相關,比如拷貝、剪切、刪除等操作。
摘要:本文介紹 WebCore 中 Loader 模塊是如何加載資源的,分主資源和派生資源分析 loader 模塊的類關系。
關鍵詞: WebKit,Loader,Network,ResouceLoader,SubresourceLoader
一、類結構及接口
Loader 模塊是 Network 模塊的客戶。 Network 模塊提供指定資源的獲取和上傳功能,獲取的資源可能來自網絡、本地文件或者緩存。對不同 HTTP 實現的適配會在 Network 層完成,所以 Loader 接觸到的基本上是同 OS 和 HTTP 實現無關的 Network 層接口。
如上是 Loader 和 NetWork 之間的類關系圖, ResourceHandleClient 是 ResourceHandle 的客戶,它定義一系列虛函數,這些虛函數是 ResouceHandle 的回調,繼承類實現這些接口。
ResouceHandleClient 的接口同網絡傳輸過程息息相關,一般為某一個網絡事件對應的回調。下面是其中的一些接口。
// 一般情況下,在發起網絡請求前調用,可以設置特定的 Http頭部,比如 user agent 等,在重定向請求的時候,也會自動調用 void willSendRequest(ResourceHandle*, ResourceRequest&, const ResourceResponse&) // 上傳數據的時候,在 TCP wrtie 事件的時候,向對方發送數據的時候調用, loader 可以根據這個回調顯示上傳進度。 void didSendData(ResourceHandle*, unsigned long long /*bytesSent*/, unsigned long long /*totalBytesToBeSent*/) // 收到第一個響應包,此時至少 http 的部分頭部已經解析(如status code ), loader 根據響應的頭部信息判斷請求是否成功等。 void didReceiveResponse(ResourceHandle*,const ResourceResponse&) // 收到 HTTP 響應數據,類似 tcp 的 read 事件,來 http 響應數據了, Network 的設計機制是來一段數據上傳一段數據。 void didReceiveData(ResourceHandle*, const char*, int,int /*lengthReceived*/) // 加載完成,數據來齊。 void didFinishLoading(ResourceHandle*, double /*finishTime*/) // 加載失敗 void didFail(ResourceHandle*, const ResourceError&) // 要求用戶鑒權 void didReceiveAuthenticationChallenge(ResourceHandle*,const AuthenticationChallenge&)
WebCore 把要加載的資源分成兩類,一類是主資源,比如 HTML 頁面,或者下載項,一類是派生資源,比如 HTML 頁面中內嵌的圖片或者腳本鏈接。這兩類資源對於回調的處理有很大的不同,比如,同樣是下載失敗,主資源可能需要向用戶報錯,派生資源比如頁面中的一張圖下載失敗,可能就是圖不顯示或者顯示代替說明文字而已,不向用戶報錯。因此有了 MainResourceLoader 和 SubresourceLoader 之分。它們的公共基類 ResourceLoader 則完成一些兩種資源下載都需要完成的操作,比如通過回調將加載進程告知上層應用。
ResourceLoader 通過 ResourceNotifier 類將回調傳導到 FrameLoaderClient 類。
主資源的加載是立刻發起的,而派生資源則可能會為了優化網絡,在隊列中等待 ( 這里的立刻發起是 loader 層面的,不是 Network 層面的 ) 。 ResourceScheduler 這個類就是用來管理資源加載的調度。主要調度對象就是派生資源,會根據 host 來影響資源加載的先后順序。
主資源和派生資源的加載還有一個區別,主資源目前是沒有緩存的,而派生資源是有緩存機制的。這里的緩存指的是 Resouce Cache ,用於保存原始數據(比如 CSS , JS 等),以及解碼過的圖片數據,通過 Resource Cache 可以節省網絡請求和圖片解碼的時候。不同於 Page Cache , Page Cache 存的是 DOM 樹和 Render 樹的數據結構,用來在前進后退的時候快速顯示頁面。
二、加載流程
下圖是加載 html 頁面時,一個正常的加載流程。
三、主資源加載過程
1. DocumentLoader 調用 MainResourceLoader::load 向 loader 發起請求
2. 調用 MainResourceLoader::loadNow
3. 調用 MainResourceLoader::willSendRequest
4. 調用 ResourceLoader::willSendRequest, 將 callback 通過 ResourceNotifier 傳導給 FrameLoaderClient 。 Client 可以在回調中操作 ResourceRequest ,比如設置請求頭部。
5. 調用 PolicyChecker::checkNavigationPolicy 過濾掉重復請求等
6. loader 調用 ResourceHandle::create 向 Network 發起加載請求
7. 收到第一個 HTTP 響應數據包 ,Network 回調 MainResourceLoader::didReceiveResponse ,主要處理 HTTP 頭部。
8. 調用 PolicyChecker:: checkContentPolicy, 並最終通過 FrameLoaderClient 的 dispatchDecidePolicyForMIMEType 判斷是否為下載請求(存在 "Content-Disposition"http 頭部)
9. 調用 MainResourceLoader::continueAfterContentPolicy ,根據 ResourceResponse 檢測是否發生錯誤。
10. 調用 ResourceLoader::didReceiveResponse ,將 callback 通過 ResourceNotifier 傳導給 FrameLoaderClient 。
11. 收到 HTTP 體部數據,調用 MainResourceLoader::didReceiveData
12. 調用 ResourceLoader::didReceiveData ,將 callback 通過 ResourceNotifier 傳導給 FrameLoaderClient
13. 調用 MainResourceLoader::addData
14. 調用 DocumentLoader::receivedData
15. 調用 DocumentLoader::commitLoad
16. 調用 FrameLoader::commitProvisionalLoad , FrameLoader 從 provisional 狀態躍遷到 Committed 狀態
17. 調用 FrameLoaderClientQt::committedLoad
18. 調用 DocumentLoader::commitData ,啟動 Writer 對象來處理數據( DocumentWriter::setEncoding , DocumentWriter::addData )
19. 調用 DocumentWriter::addData
20. 調用 DocumentParser::appendByte
21. 調用 DecodedDataDocumentParser::appendBytes 對文本編碼進行解碼
22. 調用 HTMLDocumentParser::append ,進行 HTML 解析
23. 數據來齊,調用 MainResourceLoader::didFinishLoading
24. 調用 FrameLoader::finishedLoading
25. 調用 DocumentLoader::finishedLoading
26. 調用 FrameLoader::finishedLoadingDocument ,啟動 writer 對象接收剩余數據,重復 19-22 進行解析
27. 調用 DocumentWriter::end 結束接收數據(調用 Document::finishParsing )
28. 調用 HTMLDocumentParser::finish
四、派生資源加載流程
在派生資源的加載中, SubresourceLoader 更多起到的是一個轉發的作用,通過它的 client(SubresourceLoaderClient 類)來完成操作。
各個加載階段的處理在 SubresourceLoaderClient 的派生類 CachedResourceRequest,Loader,IconLoader 中完成。 Client 會創建 SubresourceLoader 。請求發起階段, ResourceLoadScheduler 負責對 SubresourceLoader 進行調度。
Document 類會創建 CachedResourceLoader 類的對象 m_cachedResourceLoader, 這個類 ( 對象 ) 提供了對 Document 的派生資源的訪問接口 requestImage , requestCSSStyleSheet , requestUserCSSStyleSheet,requestScript,requestFont,requestXSLStyleSheet,requestLinkPrefetch。為了實現這些接口,CachedResourceLoader 需要創建 CachedResourceRequest 對象來發起請求。
一般情況下,一個 Document 擁有一個 CachedResourceLoader 類實例。MemoryCache 類則對提供緩存條目的管理,可以方便地進行 add , remove ,緩存淘汰等。具體的緩存條目則是通過 CachedResource 類存儲, MemoryCache 類維護了一個 HashMap 存儲所有緩存條目。
HashMap <String,CachedResource> m_resources;
CachedResourceRequest 依賴於 CachedResource, 在 CacheResourceRequest 的構造函數中,會傳入 CachedResource 對象作為參數。 CachedResource 既存儲響應體部,也存儲同 cache 相關的頭部。在發起請求前,會檢查是否有 cache 的 validator ,在收到響應的時候,則需要更新對應的頭部。 CachedResource 類實現了 RFC2616 中的緩存一節。實際上 CachedResource 類真正完成了同網絡的通信。 CachedResource 類根據申請的資源類型派生出不同的子類。
CachedResource 類的使用者必須是 CachedResourceClient, 在這個類中維護了 CachedResourceClient 類的集合 m_clients 。每一個 Client 通過 addClient 和 removeClient 將自己加入到該類的 Client 集合中。CachedResourceClientWalker則提供了CachedResouceClient的一個遍歷接口。當數據來齊的時候,CachedResource 類會通過 CachedResouceClient::notifyFinished 接口通知使用者。
下圖是 Image 元素對應的幾個類關系。
下面以 image 為例分析其加載過程
1. 解析 html 頁面的時候,解析到 img 標簽,調用 HTMLImageElement::create 創建 HTMLImageElement 對象,該對象包含 HTMLImageLoader 對象 m_imageLoader
2. 解析到 img 的 href 屬性,調用 ImageLoader::updateFromElementIgnoringPreviousError
3. 調用 ImageLoader::updateFromElement
4. 調用 CachedResourceLoader::requestImage
5. 調用 CachedResourceLoader::requestResource( 根據緩存的情況確定是否可以從緩存獲取,或者需要 revalidate ,或者需要直接從網絡獲取 )
6. 調用 CachedResourceLoader::loadResource
7. 根據 Resource 的類型調用 createResource 創建對應的 CachedResource
8. 調用 MemoryCache::add 在 cache 中查找是否有對應的 cache 條目,如果沒有創建之
9. 調用 CachedImage::load
10. 調用 CachedResource::load
11. 調用 CachedResourceLoader::load
12. 調用 CachedResourceRequest::load
13. 創建 CachedResourceRequest 對象,它將作為 SubresourceLoader 的 client
14. 調用 ResourceLoaderScheduler::scheduleSubresourceLoad
15. 調用 SubresourceLoader::create
16. ResourceLoadScheduler::requestTimerFired
17. 調用 ResourceLoader::start
18. 調用 ResourceHandle::create 發起請求
19. 收到 HTTP 響應頭部,調用 ResourceLoader::didReceiveResponse
20. 調用 SubresourceLoader::didiReceiveResponse
21. 調用 CachedResourceRequest::didReceiveResponse 處理響應頭部,特別是同緩存相關的頭部,比如 304 的 status code
22. 調用 ResourceLoader::didReceiveResponse
23. 收到體部數據,調用 ResourceLoader::didReceiveData
24. 調用 SubresourceLoader::didReceiveData
25. 調用 ResourceLoader::didReceiveData
26. 調用 ResourceLoader::addData 將數據存儲到 SharedBuffer 里面
27. 調用 CachedResourceRequest::didReceiveData
28. 數據來齊 , 調用 ResourceLoader::didFinishLoading
29. 調用 SubresourceLoader::didFinishLoading
30. 調用 CachedResourceRequest::didFinishLoading
31. 調用 CachedResource::finish
32. 調用 CachedResourceLoader::loadDone
33. 調用 CachedImage::data ,創建對應的 Image 對象,解碼
摘要:本文分析WebKit中html的解析過程,DOM節點樹的建立。
關鍵詞:WebKit,html解析,html tree construction,WebCore,DOM節點樹
1. HTML解析模型
圖1 HTML解析模型圖
上圖是HTML解析模型圖,HTML解析分成Tokeniser和Tree Construction兩個步驟,在”WebKit中的html詞法分析”一文中,我們已經對Tokeniser這一步進行了分析,本文的目標是Tree Construction這一步。
Tree Construction輸入是token流,輸出是DOM節點樹。
2. DOM樹
HTML DOM定義了一套標准來將html文檔結構化,它定義了表示和修改文檔所需的對象、這些對象的行為和屬性以及對象之間的關系,可以把它理解為頁面上數據和結構的一個樹形表示。
Node是DOM模型中的基礎類,它可以分成13類(見NodeType),在HTML解析中,最常見的是Document,Element,Text三類。
- Document是文檔樹的根節點,在HTML文檔中,他派生為HTMLDocument。
- 在文檔中,所有的標簽轉化為Element類,一般它有標簽名,並根據標簽名繼承為特定的子類。
- Element之間的原始文本轉化成Text類。
以一個簡單的html頁面為例:
<html> <head> <title>test</title> </head> <body> <h1>hl1</h1> <h2>hl2</h2> <h3>hl3</h3> </body> </html>
經過解析后的節點樹如下(忽略換行符):
圖2 HTML DOM節點樹示例
如果沒有忽略換行符,則每個換行符就是一個Value為”\n”的Text節點。
3. Tree Construction原理
將圖二中的節點樹以WebKit中的類具體化(同樣忽略換行符)。
圖3 Webkit HTML DOM節點樹示例
看到這里,你是不是覺得仿佛看到了一個呼之欲出的Tree Construction輪廓?是的,最簡化的情況就是這樣,根據輸入的token,創建出相應的Element派生類,然后添加到DOM樹中合適的位置,這就是Tree Construction干的事情。當然,添加到合適的位置,這個需要一系列復雜的規則,另外,WebKit將Render樹的創建也放到了Tree Construction階段中來,再加上CSS,Javascript,所以,這就是你看到的復雜的代碼。
放出兩個函數原型,熱熱身,培養培養感情。
PassRefPtr<Element> HTMLConstructionSite::createHTMLElement(AtomicHTMLToken& token); void HTMLConstructionSite::insertHTMLElement(AtomicHTMLToken& token);
Tree Construction流程由一個狀態“Insertion Mode”進行控制,它影響token的處理以及是否支持CDATA部分,HTML5中給出了詳細的規則。它也控制了在特定狀態下能夠處理的token,比如在head里面,再出現head標簽,顯然是不應該處理的。
4. 開放元素堆棧
為了維護即將解析的標簽同已解析的標簽之間的關系(此時即將解析的標簽還沒有加入到DOM樹中),引入了開放元素堆棧m_openElements,初始狀態下,這個堆棧是空的,它是向下增長的,所以最上面的節點是最早加入到堆棧中的,在html文檔中,最上面的節點就是html元素,最底部的節點就是最新加入到堆棧中的。Tree Builder的時候,每碰到一個StartTag的token,就會往m_opnElements中壓棧,碰到EndTag的token,則出棧。像Character這樣的token,則不需要進行壓棧出棧的動作,只有可以包含子節點的tag,才做壓棧出棧的動作。Html5的文檔中對開放元素堆棧也有說明。
對於正在解析的token,除了根節點html,它必然是堆棧底部元素(m_openElements.top())的子節點,所以在形成DOM樹的時候,就可以通過ContainerNode::parserAddChild這樣的接口加入到DOM節點樹中。除了正常的堆棧和壓棧,對於html,head,body元素,棧結構(HTMLElementStack)中有專門的成員m_htmlElement,m_headElement,m_bodyElement記錄,主要是用於檢錯糾錯處理。
在本文的html范例中,當解析到<h2>hl2</h2>的hl2這個character的token的時候,它的開放元素堆棧如下,HTMLHeadingElement是堆棧的top,所以它是hl2這個Text節點的parent。
圖4 開放元素堆棧示例
此時的DOM節點樹如下:
圖5 Webkit DOM節點數示例
5. 元素的創建
HTMLElementFactory類提供了元素的創建方法createHTMLElement。傳入為對應的標簽名,所屬的document,所屬的form(如果屬於form),在parser的時候,最后一個參數為true。
PassRefPtr<HTMLElement> HTMLElementFactory::createHTMLElement(const QualifiedName& qName, Document* document, HTMLFormElement* formElement, bool createdByParser);
在HTMLElementFactory中,通過一個Hash Map將tag name和對應的元素構造函數對應起來(gFunctionMap)。tag一般對應一個派生於HTMLElement的類。如下是HTMLHeadingElement的類層次結構圖。
圖6 HTMLHeadingElement類層次圖
6. 其它
HTMLConstructionSite::attach中的attach一詞,主要是attach到DOM節點數上,當然,它同時調用了Element::attach,Element類的attach主要是attach到Render樹上,它會創建對應該Element的RendrObject。
除了m_openElements,HTMLConstructionSite同時維護了Format元素列表m_activeFormattingElements,Formating元素就是那些格式化標簽,包括a,b,big,code,em,font,I,fot,I,nobr,s,small,strike,strong,tt,u。為了處理這些Formatting元素的嵌套關系(此時它們可能不是父子關系,而是平級,不加入到m_openElements),HTML5引入了這個列表。
使用gdb調試的童子,可以運行Tools/gdb/webkit.py腳本,在print結構體的時候得到易於理解的表示,還可以打印出節點樹,具體參考http://trac.webkit.org/wiki/GDB。