瀏覽器渲染引擎

漆工發表於2018-04-04

 

背景

瀏覽器的核心中主要分為渲染引擎和 javascript 引擎,本篇主要圍繞渲染引擎介紹一下瀏覽器的工作原理。

首先,我們先看幾個 user-agent 的字串:

  • Mozilla/ 1.0 (Windows NT 6.1;rv:2.0.1) Gecko/2010010Firefox/4.0.1
  • Mozilla/ 4. 0 (compatible; MSIE 7. 0; Windows NT 6. 0)
  • Mozilla/ 5. 0 (Linux; Android4. 0. 4; Galaxy Nexus Build/ IMM76B) AppleWebKit/ 535. 19 (KHTML, like Gecko) Chrome/ 18. 0. 1025. 133 Mobile Safari/ 535.

這是 3 個不同瀏覽器的 user-agent,第 1 個是 Firefox 的,第 2 個是 IE7 的,第 3 個是 Chrome 的。有沒有覺得很奇怪,為什麼所有字串的前面都會有一個 Mozilla 開頭呢? 而且在 Chrome 中包含了很多其他瀏覽器的標識,Android,Gecko,Safari … , 這些瀏覽器廠商為什麼要把這個 user-agent 字串設計成這樣?user-agent 按照正常的理解就是瀏覽器的標識,包含作業系統和瀏覽器資訊帶上版本號就行了,瀏覽器廠商為什麼搞的這麼複雜呢?

這需要了解一下 user-agent 字串歷史 ,大致的意思是, 早期的瀏覽器 Netscape 的 user-agent 是以 Mozilla/Version [Language] (Platform; Encryption) 的格式,大多數伺服器在載入頁面前都會檢查 user-agent 是否為該款瀏覽器,然而在 1995 年, IE 釋出首款瀏覽器,如果不相容 Netscape user-agent 字串,使用者就根本訪問不了頁面,於是 IE 就設計了這種格式: Mozilla/2.0 (compatible; MSIE Version; Operating System) 。 其他新的瀏覽器釋出也是一樣的,為了溶入主流而不被踢出局,在 user-agent 字串中放詳盡的資訊,以便騙取網站的信任使它與其它流行的瀏覽器相容。

接下來,簡單回顧一下瀏覽器的歷史:

  • WorldWideWeb 1991 年
  • Mosaic 1993 年
  • Netscape 1994 年
  • Opera 1995 年
  • IE 1995 年 一戰
  • Safari 2003 年
  • Firefox 2004 年 二戰
  • Chrome 2008 年
  • Edge 2015 年

很多人都以為最早的瀏覽器是 Netscape, 其實在 Netscape 之前還有 WorldWideWeb 和 Mosaic 兩個瀏覽器,WorldWideWeb 是世界上第一個瀏覽器,它同時也是一個編輯器,在 1991 年釋出, 當時 http 的版本還是 0.9 ,只支援 get 請求。

隨後在 1993 年 由美國伊利諾州的伊利諾大學的 NCSA 組織,釋出第一個可以顯示圖片的瀏覽器,叫 Mosaic。隨後 NCSA 將 Mosaic 的商業運營權轉售給了 Spyglass 公司,該公司又向包括微軟公司在內的多家公司技術授權,然後,微軟的 IE 瀏覽器就是從這裡開始了。
1994 年,在 Mosaic 瀏覽器開發團隊的核心成員,重新成立 Netscape 公司,從此 Netscape 瀏覽器誕生。
1995 年,挪威的本土電信公司 Telenor 開發了一個新的瀏覽器 Opera,Opera 瀏覽器他特點就是速度快,但是相容性不是很好,在瀏覽器上獨創了很多功能,比如標籤式瀏覽就是 Opera 發明的,Opera 在經歷很多坎坷後,在 2016 年被奇虎 360 收購。
1995 同年,微軟在取得 Spyglass Mosaic 的原始碼和授權後,釋出了首款瀏覽器 Internet Explorer ,從此展開瀏覽器第一次大戰。
1998 年,Netscape 公司內部成立 Mozilla 組織。
2003 年,蘋果公司釋出了自己第一個瀏覽器 Safari,同時在兩年後,也就是
2005 年開源了自己的瀏覽器核心 Webkit。
2004 年,Mozilla 組織釋出了 Firefox 瀏覽器,然後第二次瀏覽器大戰開始。
2008 年,是一個災難很多的一年,四川大地震,過年回家又遇上雪災,作為前端開發我的將要開始相容一個由 Goolge 開發新的 Chrome 瀏覽器。 但是 Chrome 帶來的體驗是讓人開心的 。
2015 年,微軟隨著 Windows 10 一起釋出 Edge 瀏覽器,傷透大家心的 IE ,估計維護不下去。

當前市場瀏覽器市場佔比最高的是 Chrome 瀏覽器,其次就是 IE,Firefox ,詳細佔比檢視 Net Marketshare 的統計。

瀏覽器核心

核心 瀏覽器 出生年份 JS 引擎 開源
Trident IE4 - IE11 1997 JScript,9+chakra
Gecko Firefox 2004 SpiderMonkey MPL
WebKit Safari,Chromium,Chrome(-2013) ,Android 瀏覽器,ChromeOS,WebOS 等 2005 WebCore + JavascriptCore BSD
Blink Chrome, Opera 2013 V8 GPL
Edge Edge 2015 EdgeHTML + Chakra MIT(chakra)

市面上的瀏覽器都有自己的核心,有些核心之間還存一些關係。 WebKit 是 Apple 公司在 2005 年開源的一個核心,Apple 公司早期使用的引擎是 KHTML,KHTML 是 KDE 社群維護,Apple 技術團隊也參與 KHTML 的開發,但是在開發過程中,KDE 社群不太喜歡 Apple 團隊人員提交的程式碼。所以 Apple 公司的人就自己獨立出來,在 KHTML 基礎之上建立了 WebKit 核心,並在 2005 年開源。 在 2008 年,Google 釋出 Chrome 瀏覽器,採用的也是 Webkit 核心,同時技術團隊人員也參與 WebKit 專案的開發,但是在設計上與 Apple 團隊存在分歧,所以 Google 的人就獨立出來,基於 WebKit 開發了 Blink 核心,Blink 在 Webkit 的基礎上加入多程式,沙箱等很多技術。

回過頭看看國內,有很多瀏覽器,很牛逼,都是多核的,想要相容國內銀行系統就切換到 Trident 核心,想要訪問速度就切換到 Webkit 核心,Blink 釋出以後,就把 WebKit 換成了 Blink 。

  • QQ 瀏覽器 Trident+Webkit (Blink)
  • 360 安全瀏覽器 Trident+Webkit (Blink)
  • 獵豹瀏覽器 Trident+Webkit (Blink)
  • 世界之窗 Trident+Webkit (Blink)
  • 搜狗高速瀏覽器 Trident+Webkit (Blink)
  • UC 瀏覽器 Trident+Webkit (Blink)
  • ….

WebKit 架構

接下來我們進入到 Webkit 裡面,首先看一下 WebKit 的架構圖

在上圖中實線部分,也就是 WebCore,基本上在各個瀏覽器中是共享的,虛線部分在各個瀏覽器中的存在差異。 WebCore 是渲染引擎,包含的 HTML 直譯器,CSS 直譯器,處理頁面佈局渲染等功能。 JavascriptCore 就是 WebKit 內建的 Javascript 引擎。 在最上層是 WebKit 嵌入式介面,這些介面提供給瀏覽器呼叫,但是我們可以看到圖中有 WebKit 和 WebKit2 兩個嵌入式介面,這兩有什麼區別呢?

WebKit 2 是 2010 年 4 月份釋出的,抽象出一組新的程式設計介面,給開發者用,同時採用了多程式:,一個 UI 程式,處理 Web 平臺與瀏覽器介面的程式,另外一個 Web 程式, Web 頁面渲染的程式。 讓 web 程式與 UI 程式隔離,在健壯性、安全性以及更好地使用多核 cpu 等方面帶來了好處。 以下是 WebKit 和 WebKit2 的對比圖:

更多詳細資訊可以閱讀: https://trac.webkit.org/wiki/WebKit2

在 Javascript 旁邊有很大一塊區域是 Webkit Port,所謂 WebKit Port,並沒有確切的形式,可以看作是 OS,平臺(應用程式框架),Javacsript 引擎,以及各種第三方庫的一個組合。WebKit Port 提供不同的 Port 介面供外部程式使用,

以 Webkit 為核心存在很多移植:

  • Apple’s Mac Port
  • Apple’s Windows Port
  • Cairo-based Windows Port
  • JSCOnly Port
  • WebKitGTK+ Port
  • QTWebKit

WebKit Port 通常處於以下幾種目的:

  • 使用 WebKit 作為瀏覽器(或者類似的 User Agent)的頁面解析,排版和渲染的核心,如 Safari,Chrome
  • 對 WebKit 進行封裝,對外提供構建一個瀏覽器(或者類似的 UA)的 API 介面,如 Qt
  • 以上兩者皆有,如 Android,iOS,BlackBerry

不同的 port 關注點不一樣

  • Mac 的 port 注意力集中在瀏覽器和作業系統的分割,它通過 Obj-C 和 C++ 程式碼把(WebKit)渲染引擎嵌入到本地應用中。
  • Chromium port 專注在瀏覽器。
  • QtWebKit 則把它的 WebKit 實現作為一個執行時的庫或者渲染引擎,同其跨平臺 GUI 應用程式框架一起提供給其它應用使用。 比如 無頭瀏覽器 Phantomjs 就是基於 QtWebKit 實現的。

更多詳細資訊可以閱讀:https://trac.webkit.org/wiki#WebKitPorts

接下來,我們來看一下基於 Chromium port 的瀏覽器。

在圖中我們可以看到左下角是 WebKit 核心,和他平級的還有:

  • GPU/Command Buffer , Command Buffer 是 GPU 執行緒的通訊媒介,提供 GPU 硬體加速。
  • V8,Javascript 引擎
  • 沙箱模型,瀏覽器安全保護的一種設計
  • CC(Chromium 合成器),對 RenderLayer Tree 分成渲染
  • IPC,UI,PPAPI …

在上面一層是 Content , 如果沒有 Content, 也能正常渲染,不過上面提到的這些 GPU 加速,沙箱模型,HTML 5 等功能將沒有,Content API 提供了公開穩定的介面, 目標是支援所有的 HTML5 功能和 GPU 硬體加速等功能。

“Chromium 瀏覽器” 和 “ContentShell” 是構建在 ContentAPI 之上的兩個“瀏覽器”,Chromium 具有瀏覽器完整的功能,也就是我們編譯出來能看到的瀏覽器式樣。而“ContentShell”是使用 ContentAPI 來包裝的一層簡單的“殼”,但是它也是一個簡單的“瀏覽器”,使用者可以使用 Content 模組來渲染和顯示網頁內容。 ContentShell 的作用很明顯,其一可以用來測試 Content 模組很多功能的正確性,例如渲染、硬體加速等;其二是一個參考,可以被很多外部的專案參考來開發基於“ContentAPI”的瀏覽器或者各種型別的專案。

在 Android 系統上,ContentShell 的作用更大,這是因為同它並排的左側的 “Chromium 瀏覽器” 部分的程式碼根本就沒有開源,這直接導致開發者只能依賴 ContentShell。

“Android WebView”,它是為了滿足 Android 系統上的 WebView 而設計的,其思想是利用 Chromium 的實現來替換原來 Android 系統預設的 WebView。

渲染過程

以下是瀏覽器渲染引擎及依賴模組

一個渲染引擎主要包括 HTML 直譯器、CSS 直譯器、佈局和 JavaScript 引擎等,JavaScript 引擎現在都已經獨立出來。 下面是所依賴的模組,包括網路,儲存,2D/3D 圖形,音訊和視訊,圖片解碼器等等…, 再下面就是作業系統相關的支援。

一個大致的渲染過程及依賴模組關係圖如下:

接下來我們再來看一下,在 WebKit 的渲染的詳細過程:

首先是在瀏覽器輸入 URL 以後,依賴網路模組載入各種資源,得到一個 HTML , HTML 交給 HTML 解析器進行解析,最後生成 DOM 樹,如果再解析過程中有存在 Javascript 程式碼就交給 Javascript 引擎處理,處理完成返回給 DOM 樹, 這個環節的主要目的就是構建一個 DOM 樹。

然後看一下 DOM 樹到繪製上下文

在網路資源中獲得 CSS 程式碼以後,會把 CSS 交給 CSS 解析器處理,同時會計算佈局。 DOM 樹會構建成一個 RenderObject 樹,它和 DOM 樹節點是一一對應,然後再和 解析後的 CSS 合併分析,生成 RenderLayer 樹, 這個樹就是最終用於渲染的樹,然後繪製上下文。

以下是在網上找到的幾張圖,簡單解釋了 DOM 樹到 RenderLayer 樹最終的過程。

這一段簡單的 HTML 程式碼,其中包含的 body, div canvas script 等元素,通過 HTML,CSS 解析器進行解析,最終會生成一個 RenderLayer 樹, 前面在 Chromium 架構圖的時候有提到 CC 合成器,隨著 GPU 硬體能力的增強,包括在很多小型裝置上也是如此,瀏覽器可以藉助於其處理圖形方面的效能來對渲染實現加速。此時不再將所有層繪製到一起,而是進行分層渲染,合成之後再顯示到螢幕上。

以下 RenderLayer 樹的結構,從圖中可以看出整個樹分了 3 個 Layer,在 Layer 下面包含了 RenderBlock,RenderText 等 Render 節點,每個節點上都包含的座標,大小,以及背景顏色等渲染依賴的資訊。

RenderBlock 其實就是我們 HTML 中的塊級元素,我們都知道一個元素是否為塊級元素是可以通過 CSS 改變的,所以,一個 RenderLayer 樹的結構也會根據 CSS 的變化變化,如果影響到元素的位置發生變化會都在整個樹重新計算,也是就是我們說的迴流。 關於迴流的解釋可以參考 : https://www-archive.mozilla.org/newlayout/doc/reflow.html 。

最後我們簡單瞭解一下 Shadow DOM, 在前端開發過程中大家都知道 React, Vue 這些框架中有元件的概念,一個頁面中存在很多重複的元件,在一個元件內部又存在很多基礎的 HTML 元素,這些元素可以組成一顆 DOM 樹的子樹。這樣一個元件可以被到處使用,但是問題隨之而來,那就是每個使用元件的地方都會知道這個子樹的結構。當網頁的開發者需要訪問網頁 DOM 樹的時候,這些控制元件內部的 DOM 子樹都會暴露出來,這些暴露的節點不僅可能給 DOM 樹的遍歷帶來很多麻煩,而且也可能給 CSS 的樣式選擇帶來問題,因為選擇器無意中可能會改變這些內部節點的樣式,從而導致很奇怪的介面。

那有什麼辦法可以把這些內部節點封裝起來?就像我們寫 javascript 模組化一樣,W3C 工作組提出了 Shadow DOM 的概念,比如在 HTML5 支援的 <Video> 等標籤,在其內部有很多複雜的結構, 但是播放,暫停等按鈕我們在 DOM 樹中無法直接找到相應的節點,這其實就是 Shadow DOM 的思想。 Shadow DOM 是可以通過 Javascript 自定義建立,在其內部可以維護自己的 DOM, CSS ,事件等, 具有很好的密封性。 自定義 Shadow DOM 目前只有 Chrome 支援,不過,我相信在不遠的將來 Shadow DOM 會給元件化開發帶來更多美好的體驗。

參考資料


相關文章