上一次說到通過管道把接收到的HTTP資料通知另一個線程處理,它不是直接發送資料過去,而是把資料在共用記憶體裡的控制代碼發送過去,達到高效通訊的目的。下面就來分析資源處理進程裡,接收到這個訊息之後,做些什麼處理。這個訊息的處理代碼如下:
#001 void ResourceDispatcher::OnReceivedData(int request_id,
#002 SharedMemoryHandle shm_handle,
#003 int data_len) {
#004 // Acknowlegde the reception of this data.
回應這個訊息,說已經收到資料了。
#005 IPC::Message::Sender* sender = message_sender();
#006 if (sender)
#007 sender->Send(
#008 new ViewHostMsg_DataReceived_ACK(MSG_ROUTING_NONE, request_id));
#009
#010 DCHECK((shm_handle && data_len > 0) || (!shm_handle && !data_len));
開啟共用記憶體檔案,使用唯讀方式。
#011 SharedMemory shared_mem(shm_handle, true); // read only
#012
尋找到請求下載的資源的請求標識號。
#013 PendingRequestList::iterator it = pending_requests_.find(request_id);
如果沒有找到相應的請求標識號,就直接返回,不用處理這些資料。
#014 if (it == pending_requests_.end()) {
#015 // this might happen for kill()ed requests on the webkit end, so perhaps
#016 // it shouldn't be a warning...
#017 DLOG(WARNING) << "Got data for a nonexistant or finished request";
#018 return;
#019 }
#020
這裡找到相應的請求標識號,就把資料放到請求資訊裡處理。
#021 PendingRequestInfo& request_info = it->second;
#022
#023 if (data_len > 0 && shared_mem.Map(data_len)) {
#024 RESOURCE_LOG("Dispatching " << data_len << " bytes for " <<
#025 request_info.peer->GetURLForDebugging());
#026 const char* data = static_cast<char*>(shared_mem.memory());
#027 request_info.peer->OnReceivedData(data, data_len);
#028 }
#029 }
上面這個函數實現接收到HTTP資料,並且把資料放到請求的緩衝區裡,但它沒有知道什麼時候接收資料完成,顯然有另外一個訊息來做這些的工作,就是下面類ResourceDispatcherHost的函數:
#001 bool OnResponseCompleted(int request_id, const URLRequestStatus& status) {
#002 receiver_->Send(new ViewMsg_Resource_RequestComplete(
#003 routing_id_, request_id, status));
#004
#005 // If we still have a read buffer, then see about caching it for later...
#006 if (spare_read_buffer_) {
#007 read_buffer_.reset();
#008 } else if (read_buffer_.get() && read_buffer_->memory()) {
#009 spare_read_buffer_ = read_buffer_.release();
#010 }
#011 return true;
#012 }
這個函數裡通過發送訊息ViewMsg_Resource_RequestComplete來通知資源進程已經把網路的資料接收完成了,可以進入下一步處理。然後在資源進程裡就會處理這個訊息,下一次再來分析這方面的代碼。
上一次說到在類ResourceDispatcher會收到接收HTTP資料訊息,並進一步處理資料。那麼ResourceDispatcher類又把接收到的資料發往何處呢。這是需要我們去搞懂它的。通過進一步的跟蹤,會發現在ResourceDispatcher::OnReceivedData函數調用WebCore::ResourceHandleInternal類來處理,也就是把接收到的資料拋給WebCore來處理了。如下面的代碼:
#001 void ResourceDispatcher::OnReceivedData(int request_id,
#002 SharedMemoryHandle shm_handle,
#003 int data_len) {
#004 // Acknowlegde the reception of this data.
#005 IPC::Message::Sender* sender = message_sender();
......
#023 if (data_len > 0 && shared_mem.Map(data_len)) {
#024 RESOURCE_LOG("Dispatching " << data_len << " bytes for " <<
#025 request_info.peer->GetURLForDebugging());
#026 const char* data = static_cast<char*>(shared_mem.memory());
#027 request_info.peer->OnReceivedData(data, data_len);
#028 }
#029 }
上面第27行代碼就是調用webcore裡類ResourceHandleInternal::OnReceivedData函數,這樣就把資料儲存到webcore裡面,也就是webkit裡面了。經過如下面的調用過程:
1) WebCore::ResourceLoader::didReceiveData
2) WebCore::SubresourceLoader::didReceiveData
3) WebCore::Loader::didReceiveData
4) WebCore::CachedImage::data
這裡就是把映像的資料緩衝起來,以便後面調用渲染引擎來顯示。這一次就分析到這裡,總算把資料怎麼樣放到webkit裡搞清楚了,下一次再來看看webkit是怎麼樣把資料顯示出來的。
上一次說到映像緩衝起來,其實很多情況下是文本的顯示,也就是HTML的解釋。要把網頁顯示出來,肯定是先從HTTP裡收到網頁資料,然後再使用HTML分析器來解釋HTML語言,最後根據HTML來產生所有可以顯示的元素,再由於這些元素產生BMP位元影像,這樣只需要把BMP位置顯示到視窗裡就萬事大吉了。這個過程看起來簡單,其實是一個非常複雜的過程,現在就帶你去深入地分析這個過程,就基本把Webkit的過程搞清楚了,同時也把chrome分析網頁的過程搞清楚了。這個過程如下:
1) ResourceDispatcher::OnReceivedData() 資源指派類接收到網頁資料。
2) WebCore::ResourceHandleInternal::OnReceivedData() WebCore::ResourceHandleInternal類接收到資料。
3) WebCore::ResourceLoader::didReceiveData() 資源載入類接收到資料。
4) WebCore::MainResourceLoader::didReceiveData() 主資源類接收到資料。
5) WebCore::MainResourceLoader::addData() 主資源類儲存資料。
6) WebCore::FrameLoader::receivedData() 架構載入類儲存資料。
7) WebCore::DocumentLoader::receivedData() 文檔載入類儲存資料。
8) WebCore::DocumentLoader::commitLoad() 文檔載入類提交所有接收的資料。
9) WebCore::FrameLoader::committedLoad() 架構載入類提交資料。
10) WebFrameLoaderClient::committedLoad() 網頁架構載入類提交資料。
11) WebFrameImpl::DidReceiveData() 網頁架構實作類別儲存提交的資料。
12) WebCore::FrameLoader::addData() 架構載入類儲存資料。
13) WebCore::FrameLoader::write() 把網頁資料寫入HTML緩衝。
14) WebCore::HTMLTokenizer::write() HTML終結符分析器進行儲存。
15) WebCore::HTMLTokenizer::processToken() HTML終結符分析器分析HTML資料。
16) WebCore::HTMLParser::parseToken() HTML分析器分析網頁資料。
17) WebCore::HTMLParser::insertNode() 分析到一個網頁裡的節點,開始插入。
18) WebCore::Text::attach() 發現一個文本節點並儲存。
19) WebCore::Node::createRendererIfNeeded() 建立可以渲染的節點。
20) WebCore::Text::createRenderer() 開始建立文本渲染對象。
21) WebCore::RenderText::RenderText() 建立文本渲染對象RenderText。
從上面的過程,可以看到分析過程是比較複雜的,不過,總算把分析網頁資料這個主線抓住了,其它的東西,都是為了這條主線而進行的。只要跟著這條主線,把相應的類再進一步分析,就可以把整個程式搞得一清二楚了。在最後一步裡,就會產生RenderObject對象,而所有的RenderObject對象是根據分析HMTL產生一棵樹來儲存起來。當介面上要顯示出來時,其實就是去遍曆整個RenderObject對象樹。下一次再來分析介面怎麼樣顯示這些對象的。
通過上一次的分析,我們看到所有網頁資料經過HTML分析器之後,都會變成一個一個RenderObject對象,那麼這些RenderObject對象又是怎麼樣顯示到介面上面的呢。現在就帶著這個疑問來分析下面的代碼,這樣肯定會找到解決方案的。怎麼樣找到入口呢。其實可以先從介面顯示的類開始,可以看到顯示介面的視窗類別名稱叫做Chrome_RenderWidgetHostHWND,有了這個類名稱,就可以到代碼裡查看它在那裡了。
#001 class RenderWidgetHost;
#002 class WebMouseEvent;
#003 class WebCursor;
#004
#005 typedef CWinTraits<WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0>
#006 RenderWidgetHostHWNDTraits;
#007
#008 static const wchar_t* const kRenderWidgetHostHWNDClass =
#009 L"Chrome_RenderWidgetHostHWND";
可看到這個視窗類別名稱是定義在這裡,再跟著kRenderWidgetHostHWNDClass來尋找,就會找到顯示視窗,如下:
#001 class RenderWidgetHostHWND :
#002 public CWindowImpl<RenderWidgetHostHWND,
#003 CWindow,
#004 RenderWidgetHostHWNDTraits>,
#005 public RenderWidgetHostView {
#006 public:
#007 RenderWidgetHostHWND(RenderWidgetHost* render_widget_host);
#008 virtual ~RenderWidgetHostHWND();
#009
#010 void set_close_on_deactivate(bool close_on_deactivate) {
#011 close_on_deactivate_ = close_on_deactivate;
#012 }
#013
#014 void set_parent_hwnd(HWND parent) { parent_hwnd_ = parent; }
#015
#016 DECLARE_WND_CLASS_EX(kRenderWidgetHostHWNDClass, CS_DBLCLKS, 0);
通過上面的分析,就可以找到顯示網頁的視窗類別RenderWidgetHostHWND,在這個類裡,主要顯示的位置是在void RenderWidgetHostHWND::OnPaint(HDC dc)函數裡面,它的代碼如下:
#001 void RenderWidgetHostHWND::OnPaint(HDC dc) {
#002 DCHECK(render_widget_host_->process()->channel());
#003
#004 CPaintDC paint_dc(m_hWnd);
#005 HBRUSH white_brush = reinterpret_cast<HBRUSH>(GetStockObject(WHITE_BRUSH));
#006
#007 RenderWidgetHost::BackingStore* backing_store =
#008 render_widget_host_->GetBackingStore();
#009
#010 if (backing_store) {
#011 gfx::Rect damaged_rect(paint_dc.m_ps.rcPaint);
#012
#013 gfx::Rect bitmap_rect(
#014 0, 0, backing_store->size().width(), backing_store->size().height());
#015
#016 gfx::Rect paint_rect = bitmap_rect.Intersect(damaged_rect);
#017 if (!paint_rect.IsEmpty()) {
#018 BitBlt(paint_dc.m_hDC,
#019 paint_rect.x(),
#020 paint_rect.y(),
#021 paint_rect.width(),
#022 paint_rect.height(),
#023 backing_store->dc(),
#024 paint_rect.x(),
#025 paint_rect.y(),
#026 SRCCOPY);
#027 }
......
#058 }
其實這個函數是通過如下發送訊息給另一個進程進行渲染成BMP的圖片,
Send(new ViewMsg_Repaint(routing_id_, view_size));
那麼誰來接收ViewMsg_Repaint訊息呢。繼續細心地尋找,就到在如下類函數裡處理:
void RenderWidget::OnMsgRepaint(const gfx::Size& size_to_paint)
在這個函數,並不是最終的結果,它又會調用其它線程來處理渲染,以便達到非同步結果。它的調用過程如下:
1) RenderWidget::DoDeferredPaint() 線程裡開始渲染網頁顯示
2) RenderWidget::PaintRect() 視窗裡開始進行顯示
3) WebViewImpl::Paint() web視類開始顯示。
4) WebFrameImpl::Paint() web架構類開始顯示。
5) WebCore::ScrollView::paint() 滾動視窗顯示。
6) WebCore::Frame::paint() WebCore裡的架構顯示。
7) WebCore::RenderLayer::paint() 分層顯示。
8) WebCore::RenderLayer::paintLayer()
9) WebCore::RenderBlock::paint() 在每一層裡顯示每一塊地區。
10) WebCore::RenderBlock::paintObject() 顯示這一地區的對象。
11) WebCore::RenderBlock::paintContents() 顯示需要顯示的內容。
12) WebCore::RenderFlow::paintLines() 這裡需要顯示文字。
13) WebCore::RootInlineBox::paint() 開始顯示一行文字。
14) WebCore::InlineFlowBox::paint() 進行一行文字流向。
15) WebCore::InlineTextBox::paint()
16) WebCore::GraphicsContext::drawText() 進行一個一個文字顯示。
17) WebCore::Font::drawText() 這裡調用字型類來把文字的編碼變成位元影像。
18) WebCore::Font::drawSimpleText() 這裡把位元影像顯示到介面記憶體裡。
通過上面的分析,可以看到顯示一串文字的過程是如此複雜的過程。其它圖片顯示的過程也是一樣,都把它們變成位元影像,然後再分層顯示出來。那麼JavaScript是怎麼樣顯示的呢。這個會比上面的過程更加複雜,後面再仔細地分析它。下一次,主要仔細地看看這些過程裡的一些類功能。