上千萬行,十多G源碼,瀏覽器為什么這么“變態”?
我們來看開源的chromium,這貨確實相當相當的復雜。源碼拉下來就有十多G。
我們不禁好奇,chromium到底有哪些玩意,為啥平時感覺只是顯示個網頁、幾句HTML而已,怎么會需要這么多代碼?
第一眼從目錄結構上,chromium包含這些東西:
base,通用代碼,基礎組件,包含字符串、文件、線程、消息隊列等工具類集合。
cc,Chromium compositor 的縮寫,負責渲染合成。
chrome,Chromium 瀏覽器外殼實現。
content,多進程沙盒瀏覽器的核心代碼,管理進程架構和線程架構。
gpu,OpenGL 封裝代碼,包含 CommandBuffer 和 OpenGL 兼容性支持等。
net,網絡棧實現。
ipc,進程間消息通信實現。
media,多媒體封裝代碼,包含了媒體內容捕獲和播放的組件集合。
mojo,類似于 Android 的 AIDL,提供了跨語言(C++ / Java / JavaScript)跨平臺的進程間對象(Object)通信機制;。
skia,圖形庫,這里存放的是 Chromium 對 skia 的 配置和擴展代碼,另有 third_party/skia 目錄存放原生的 skia 代碼。
third_party,網頁排版引擎。第三方庫
ui,UI 框架。
v8,V8 JavaScript 引擎庫。
看起來還好吧?但實際上,這里面每一個展開來講,都是一本厚厚的工具書的容量。
比如net,看起來只是個網絡庫,然而里面包含主機解析,cookies,網絡改變探測,SSL,資源緩存,ftp,HTTP, OCSP實現,代理 (SOCKS和HTTP) 配置,解析,腳本獲取(包括各種不同系統下實現),QUIC,socket池,SPDY,WebSockets……里面每一項展開來講,就是一本書。
v8層,看起來功能很單一,只是實現一下js嘛,但里面包括字節碼解析器,JIT 編譯器,多代GC,inspector (調試支持),內存和 CPU 的 profiler(性能統計),WebAssembly 支持,兩種 post-mortem diagnostics 的支持,啟動快照,代碼緩存、代碼熱點分析……里面每一項展開來講,又是一本書,還是難坑的編譯原理和優化方向。
Skia,看起來只是個圖形庫嘛,用點畫出各種圖。然而里面包括十幾種矢量的繪制,文字繪制、GPU加速、矢量的指令錄制以及回放(還要能支持線程安全)、各種圖像格式的編解碼、pdf的生成(這個是個隱藏的很深的功能,但很有趣。Skia支持把矢量圖繪制成pdf)、GPU渲染優化(既以上部分功能需要用gpu來渲染)……里面每項展開來講,又是一本書。
另外值得一提的是,skia是谷歌收購的。不知道谷歌是覺得自己沒實力做,還是太費功夫。總之谷歌選擇了直接買別人的代碼來完成這些功能。
ui,看起來只是一套UI 框架嘛。然而chromium需要一套全平臺適配的ui庫,還要能支持gpu加速。不過可惜的是里面沒實現richedit。ui庫的設計,深入來做,其實可以說又是個瀏覽器了。
等一下,以上這些,看起來只是瀏覽器的外層。我們最關心的網頁排版呢?這個難道不是瀏覽器的核心嘛!
是的,神奇的是,chromium把排版引擎blink放到了third_party下,而且架構上真的當成了一個第三方庫一樣對待。
據谷歌的員工說,這是歷史原因……好吧姑且信了。然而這個第三方庫,成了當之無愧的最復雜,功能最重要的第三方庫。
blink的工作包括:
- 實現web平臺的規范(例如,HTML標準),包括DOM,CSS和Web IDL
- 配合V8運行JavaScript
- 從底層網絡堆棧請求資源
- 構建DOM樹
- 計算樣式和布局
- 請求chrome compositor(上文提到的cc層)并繪制圖形。
說起來簡單。看一下現在的HTML、CSS規范,各種細節加起來……有快上萬頁。
除了chromium layout組、firefox的開發人員等,我想沒幾個人會去仔細閱讀并一個個的實現這些規范吧。光是看目錄和文字描述,就頭大了,更別說要完整的實現出來。
往往一個簡單的display:gird\flex背后就是龐大復雜的計算,而且還要充分考慮性能上如何優化,滾動時如何更快的展示…
另外排版還需要支持世界各國的奇奇怪怪的文字。例如從右往左寫、規則復雜無比阿拉伯文。相比之下,漢字這種方塊字的排版簡直就是弟弟。還有各種奇怪的unicode字符。
怎么能處理好這些字符和語言,并配合幾千頁的html、css排版規則正確顯示出來……這是個極度燒腦的事情。
我們再從排版這個大泥坑里跳出來看看外面別的東西。這時候你會發現……外面的泥坑好像更大。隨便說幾個,比如:
多進程框架。嗯,你需要更多的進程來渲染更多的網頁,這樣才能崩潰了也不影響其他網頁。
注意,chromium把渲染排版放在渲染進程,但繪制到窗口又是主進程。這里面少不了各種跨進程通信、同步。對于代碼的編寫以及調試,是個很考驗編程功底的事情。
webrtc。網絡視頻相關。又是一個被收購的庫。關于webrtc,你需要知道它能實現多人實時語音、降噪、網絡傳輸視頻、攝像頭的捕獲,音頻算法實現(比如 fft),視頻算法實現(比如 h264 協議格式), Socket、線程、鎖等基礎庫(是的,webrtc也造了套自己的輪子)。又是個龐大的組件。
密碼管理、下載管理、擴展管理。
一套調度整個多進程框架以及blink的核心層。在chromium被稱之為content層,負責處理一切繁瑣的細節。例如各種系統、平臺的鼠標鍵盤消息派發,歷史棧(前進后退),頁面緩存。
沙箱機制。負責隔離以及降低子進程的權限。沙箱的實現上,在不同系統做了諸多hook操作。
chrome相關的外殼及應用。例如我們常見的標題欄、url欄,webui如設置頁、歷史記錄頁。對,其實chrome單詞的原意就是這個。
Clound_Print,谷歌云打印相關,提供谷歌瀏覽器頁面預覽打印清單。
Courgetter,谷歌提供的二進制文件對比核心算法,用于比較不同版本的二進制差異。谷歌為了方便升級,搞了套升級策略和算法。
神奇的syzygy優化。是的,谷歌也嫌chrome太大了、加載太慢了。于是他們開發了一套工具鏈,優化重排布PE二進制文件的算法來達到優化程序。Chrome瀏覽器應用了Syzygy優化之后,程序冷啟動的頁面調度(paging traffic)優化了80%,加載的Image的Working Set優化了40%。簡單的說,谷歌為了優化啟動性能,從編譯器上對exe、dll開始做手腳了。
Media,Chrome的多媒體模塊,支持音頻播放和錄音等功能。這里用到了ffmpeg。但在ffmpeg外,為了和blink配合,又是包裹了厚厚的一層,用來處理好渲染管線。另外MSE API也花了不少功夫。
swiftshader。很有趣的一個模塊,用純軟件的代碼,完整實現了opengl的接口。可以在沒有硬件加速的機器上跑起opengl。也是個龐大的庫,而且也是被收購的。看起來谷歌對圖形學方面的很多工程似乎不擅長?還是不想覺得應該交給更專業的團隊去做。
gn、gyp、ninja。chromium為了更方便的管理編譯,自己擼了三套輪子。類似makefile、cmake,然后底層調用ninja再到vs或者clang負責具體編譯其他的點還有很多很多,以后想到了再補充。
總之,以上隨意一個點,要正確的實現,都是一個團隊的工作量,都可以寫成一本書。然而chromium把他們全部實現了,而且還在不停的加入新的功能。
看到這里,大家應該明白為啥強如微軟,也放棄維護他們自己的瀏覽器內核了。因為需要投入的人力財力實在是太恐怖了。chromium團隊,光是開發人員,都已經上千了。
假如每個人員年薪是100w 人民幣,持續投入十年,這個支出就是幾十億,這還不算周邊的測試、產品、UI。
最關鍵的是,就算微軟愿意投入十億,能保證做到chromium相同的功能嗎?就算能做到相同的功能,還不是另外一套chromium,能做出其他優勢嗎?
于是最后微軟也放棄了,干脆直接從開源的chromium上改起,把微軟需要的功能融入chromium。
所以,chromium的霸權就是這么來的。看似開源免費,實則把所有開發者和對手,緊緊的捆綁在自己周圍。
好了,現在吐槽開始了。
chromium稱霸瀏覽器界以來,看起來開源,誰都可以拿去改。然而比起它的前輩,我覺得從“道德”上,chromium要“差”很多。
最讓我受不了的一點是,chromium在無盡的往里面塞功能的時候,很少想過是否別人可以輕易的移除它們。
chromium代碼號稱模塊化、高內聚低耦合,然而如果你想砍掉一些不需要的東西,對不起,沒有宏控制,手動刪代碼吧。有幾個人能有精力一點點的去掉里面這些繁瑣的功能呢?
這就導致一個問題,需要chromium某一部分功能的人,必須被強塞進一堆谷歌認為你需要的東西。對比之下,為啥我說比起chromium的前輩要差很多呢,其實我指的正是webkit。webkit最讓人欣賞的一點就是它在專注實現內核的同時,大部分功能都是可“拆卸”的。有宏可以關閉。甚至連svg這種排版上的小功能都有宏可以關閉。
而chromium,如果我需要排版、音視頻,但不需要多進程呢?如果我需要音視頻但不需要webrtc呢?對不起,谷歌沒這考慮。要帶就全都帶上吧,不帶你自己砍代碼吧。
等你辛苦幾個星期砍完代碼,谷歌告訴你,我都更新了2、3個版本啦,你要不要更新下代碼?哦?新版本chromium架構又大改了?哭了……
這也就造成現在基于chromium的一堆開發框架,如electron、cef、nwjs,全都動不動100多M的大小。因為從chromium的框架設計上,就很難把那些極其龐大復雜的細節功能排除掉。
這些功能對于谷歌作為一個瀏覽器來說,當然是必要的。然而回到本問題,“瀏覽器內核”真的需要這么復雜嗎?
瀏覽器需要這么復雜,這是真的;然而作為一個瀏覽器內核提供給一些sdk給別人用,也需要這么復雜嗎?我們用electron寫一套進供銷管理系統的客戶端,你會需要帶上幾十M的webrtc、webgl、多媒體播放、天城文支持嗎?
最后打破這個疑問的,是我很多年后進入了QQ瀏覽器的移動端組(其實就是x5內核,微信上被大家吐槽最多的那個)。當年的x5內核其實是基于webkit改造的。從chromium回到webkit,我突然有種豁然開朗的感覺。
回到問題開頭,從瀏覽器內核的角度,其實沒那么復雜,只要做好網絡、排版、渲染,就足以應付大部分使用場景了。
這讓我想起瀏覽器早期年代,群雄爭霸的時代,那時候瀏覽器內核很小。從幾百K到幾M的瀏覽器都有。我記得早年的移動設備上跑的瀏覽器,css支持的都不好,不過特別小巧,有的才幾百K而已。
然而后來組里架構調整,x5內核為了跟上時代,從webkit切回了chromium(也是因為被罵了太多了,當時x5號稱移動端IE6,做過微信相關開發的人應該深有體會)。
對chromium深惡痛絕的我,當時有了一個大膽的想法。把排版引擎blink(也就是webkit在chromium里的繼承者)重新從chromium里剝離出來,再補上一些周邊的設施、組件,再次成為一個完整獨立的瀏覽器內核。
當然我還是有自知之明的。一個瀏覽器內核而已,不可能實現chromium同樣的功能。但能把排版、渲染、網絡、視頻實現,就差不多了。
其實我也不是想做個瀏覽器,而是想專注內核這塊,做一個提供給第三方app嵌入的內核。作為一個內核,其實不需要上面講的所有那一大堆功能。比如,網絡層,大部分人不需要什么網絡改變探測,ftp,OCSP實現,代理配置、解析、腳本獲取,QUIC,socket池,SPDY什么的。大部分人僅僅需要一個http的實現,可以拉取到服務器資源。
我用300k的curl代替了十余M的chromium net庫,并工作良好。少了的功能可以用插件形式補上嘛。一些和排版渲染無關的功能,我都打算做成插件。例如音視頻、webgl、webrtc、多進程等。
目前這個項目已經擼了5年了,開源在github上,提供C接口方便其他語言調用。整個編譯出來就是一個1-20m左右的dll(視編譯選項而定),甚至還包含了一個electron的精簡實現:
https://github.com/weolar/miniblink49。