摘要:本文介紹 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 等,在重定向請求的時候,也會自動調
用
voidwillSendRequest(ResourceHandle*, ResourceRequest&,const
ResourceResponse&)
// 上傳數據的時候,在 TCP wrtie 事件的時候,向對方發送數據的
時候調用, loader 可以根據這個回調顯示上傳進度。
voiddidSendData(ResourceHandle*, unsigned long long
, unsigned long long)
// 收到第一個響應包,此時至少 http 的部分頭部已經解析(如
statuscode ), loader 根據響應的頭部信息判斷請求是否成功
等。
voiddidReceiveResponse(ResourceHandle*,
const ResourceResponse&)
// 收到 HTTP 響應數據,類似 tcp 的 read 事件,來 http 響應數據
了, Network 的設計機制是來一段數據上傳一段數據。
voiddidReceiveData(ResourceHandle*, const char*, int,
int)
// 加載完成,數據來齊。
voiddidFinishLoading(ResourceHandle*, double )
// 加載失敗
voiddidFail(ResourceHandle*, constResourceError&)
// 要求用戶鑑權
voiddidReceiveAuthenticationChallenge(ResourceHandle*,
constAuthenticationChallenge&)
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 對象,解碼