WebKit 核心原始碼分析 ( 四 )
摘要:本文介紹 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.