Chrome源碼剖析 【五】

【五】 Chrome的插件模型

1. NPAPI

爲了緊密的與各個開源瀏覽器團結起來,共同抗擊IE的壟斷,Chrome的插件,也遵循了NPAPI(Netscape Plugin Application Programming Interface)標準,支持這個標準的瀏覽器需要實現一組規定的API供插件調用,這組API形如NPN_XXX,比如NPN_GetURL,插件可以利用這些API進行二次開發。而NPAPI插件以一個Dll之類的作爲物理載體(windows下dll,linux下是so...)進行提供,裏面同樣也實現了一組規定的API。形式包括NP_XXXNPP_XXX,NP_XXX是系統需要默認調用的方法,用於認知這個插件,比如NP_Initialize, 而NPP_XXX是用於插件完成一些實際功能,比如NPP_New。。。
所有的插件dll都需要放置在指定目錄下(根據操作系統的不同而不同...),每個插件可以處理一種或多種MIME格式的數據,比如application/pdf,說明該插件可以處理pdf相關的文檔。在Chrome中鍵入about:plugins,可以查看當前Chrome中具有的插件信息。。。
NPAPI是一個很經典的插件方案,用dll進行注入,用協定的API進行通信,用字符串描述插件能力。插件宿主(在這裏就是瀏覽器...),會根據能力描述,動態加載插件,並負責插件調用的流程和生命週期管理。而插件中,負責真實邏輯的處理,並可以構造UI與用戶交流。以此類方式實現的插件系統,往往是處理的邏輯比較固定適用範圍一般(用API寫死了邏輯...),但可擴展性不錯(用字符串描述能力,可無限擴展...)。。。
在Chrome中nphostapi.h中,定義了所有NPAPI相關的函數指針和結構,這個文件放置在glue目錄下,如果看過前面碰過的文章就知道,在WebKit內肯定也有一套相同的東西;在npapi.h/.cc中,提供了Chrome瀏覽器端的NPN_XXX系列函數的實現;每一個插件物理實例,用PluginLib類來表示,而每一個插件的邏輯實例,用PluginInstance類來表示。這個概念牽強附會的可以用windows中的句柄來類比,當你想操作一個內核對象,你需要獲得一個內核對象的句柄,每個進程中的句柄肯定不相同,但後面的內核對象卻是同一個,內核對象的生命週期通過句柄的計數來控制,有人用則或,無人用則死(當然這個類比相當的牽強,主要是想說明引用計數和邏輯與物理的關係,但一個關鍵性的區別在於,PluginLib與PluginInstance都是在一個進程內的,不能跨越進程邊界...)。在Chrome中,PluginLib負責加載和銷燬一個dll,拿到所有導出函數的函數指針,PluginInstance對這些東西進行了封裝,可以更好的來調用。。。
關於NPAPI的更多細節,Chrome並沒有提供任何文檔,但是,各個先驅的瀏覽器們都提供了大量豐富的文檔。比如,你可以到這裏,查看firefox中的NPAPI文檔,基本通用。。。

2. Chrome的多進程插件模型

Chrome的插件模型,與早先的瀏覽器的最大不同,是它採用了多進程的方式,每一個插件,都有一個單獨的進程來承載(Shift + Esc打開Chrome進程管理器,可以看到現在已經加載的插件進程...)。當WebKit進行頁面渲染的時候,發現了未知的MIME類型數據,它會告知給Browser進程,召喚它提供一個插件來解析。如果該插件還未加載,Browser會在指定目錄中搜尋出具有此實力的插件(如果沒有此類人才只能作罷...),併爲它創建一個進程,讓它負責所有的該插件相關的任務,然後建立起一個IPC通路,與它“保持通話”。這套流程一定不會太陌生,因爲它與Render進程的創建大同小異換湯不換藥。。。
Plugin進程與Render進程最大的區別在於,Render需要與Browser進程大量通信,因爲它的HWND歸Browser老大掌管着,相關所有內容都需要通信完成。但Plugin不需要與Browser頻繁聯繫,它大部分的通信都是與Render進程發生的。如果Plugin與Render之間的通信,還需要走Browser中轉一下,這就顯得有些***子放屁了,雖然Browser是大頭,但不是冤大頭,它不會幹這種吃力不討好的事情。他只是做了一回Render與Plugin間的媒婆而已。當Plugin與Browser建立好了IPC通路後,它會讓Render建立一個新IPC通路,用以與Plugin通信,IPC的有名管道名,經由Browser通知給Plugin。完成名字協商後,Render與Plugin的通信關係就建立好了,它們之間就可以直接進行通信了。。。
整個通信模式,可以看這裏。這是一個很標準的代理模式的應用,稍有了解的都可以跳過我後面會做的一段羅嗦的描述,一看官方文檔中的圖便能知曉。在Render進程端,WebPluginImplWebPlugin的一個子類,WebPlugin是供Webkit進行調用的一個接口,利用依賴倒置,實現了擴展。在Plugin進程端,實現了一個WebPluginDelegateImpl類,該類會調用PluginInstance的相關接口實現真實的插件功能。這樣的話,只需要WebPluginImpl調用WebPluginDelegateImpl中的相應方法,就可以實現功能。但問題是WebPluginImpl與WebPluginDelegateImpl天各一方各處於一個進程,很顯然,這裏需要一個代理模式。這裏沿用了COM的架構,Delegate + Stub + Proxy。WebPluginImpl調用代理WebPluginDelegateProxy,該代理會將調用轉換成消息,通過IPC發送給Plugin進程,在Plugin端,通過WebPluginDelegateStub監聽消息,並轉換成對真實WebPluginDelegateImpl的調用,從而完成了跨進程的一個調用,反之亦然。。。

3. Chrome的可擴展性

總所周知,firefox通過三種方式進行自定義,插件、擴展和皮膚。其中,插件是使得瀏覽器能用,不會出現一大塊一大塊的無法顯示的區域;擴展是使得瀏覽器好用,可以簡單方便的進行功能的定製和個性化配置;皮膚是幫助瀏覽器變得好看,畢竟羅卜白菜,給有所愛。。。
與之對比,來看Chrome。Chrome有了插件,有了皮膚,但是沒有擴展。這就意味着,你很難爲Chrome定製一些特色的功能。目前,所有對Chrome的功能擴展,都是通過書籤抑或是修改內核來實現的。前者能力太弱,後者開發起來太麻煩,容易出錯不提,還必須要與時俱進,跟上版本的變化,並且還不能自由的選擇或關閉。因此,這都不是長遠之計,Chrome提供一套類似於firefox的擴展機制,也許纔是正道。據傳說,Chrome團隊正在琢磨這件事,不知道最終會出來個怎麼樣的結果,是盡力接近firefox降低移植成本,還是另立門戶特立獨行,我想可以拭目以待一把。。。
在多進程模式下,Chrome的插件還有一個問題,前面提到過,就是關於UI控件的。由於NPAPI的標準,是允許插件創建HWND窗口的,這就使得當Plugin繁忙,且Browser進程發起HWND的同步的時候,主進程被掛起,這個瀏覽器停滯。在Render進程中,解決這個問題的思路是控制權限,不然Render創建HWND,到了Plugin中,這招不能使用,只能夠使用另一招,就是監管。不停的檢查Plugin是否太繁忙,無法響應,一旦發現,立即殺死該Plugin及其所處的頁面。這就好比你想解決奶中有三氯氰胺的問題,要麼控制奶源,不從奶站購買全部用自家的,要麼加強監管,提高檢查力度防止隱患。兩種策略的優缺點一眼便知,依照不同環境採取不同策略即可。。。
總體說來,Chrome的可擴展性着實一般,不過Chrome還處於Beta中,我們可以繼續期待。。。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章