剖析極速 CSS 引擎:Quantum CSS (原名Stylo)(上)

Mozilla Taiwan
6 min readOct 16, 2017

--

你可能聽過「量子專案」(Project Quantum),這是為了大幅加快 Firefox 速度而推動的全面改造 Firefox 引擎的計畫。我們一方面把部分引擎元件改為實驗性瀏覽器 Servo 的元件,另一方面,也針對其他元件進行大規模改良。

有人說,我們推動這個專案,就像幫飛行中的飛機換引擎一樣。但我們還是堅守目標,一個接一個元件地改進,希望透過每個元件的上線,陸續將 Firefox 的改變呈現在你的面前。

第一個源自 Servo 的重大元件更新 —名為 Quantum CSS(原名 Stylo)的全新 CSS 引擎 —現已於 Nightly 版本釋出以供測試。如果你想體驗和測試的話,請進入 about:config,找到 layout.css.servo.enabled,並將其設置為 true。

這個新引擎共運用到四個不同瀏覽器的尖端創新技術,才打造出有別於以往的全新超高速 CSS 引擎。

它善用現代硬體的優勢,運用使用者機器內所有核心來平行處理運算負載,進而達成執行速度加快 2 或 4、甚至高達 18 倍的成果。

以外,它還結合了其他瀏覽器的最新優化技術,所以,就算沒有平行運算,它仍舊會是疾如雷電的引擎。

CSS 引擎到底有何能耐?首先,我們先來看看 CSS 引擎,以及它如何與瀏覽器其他元件搭配。然後,我們再進一步了解 Quantum CSS 能如何使 CSS 引擎跑得更快。

CSS 引擎功用何在?

CSS 引擎是此瀏覽器渲染引擎的一部分。渲染引擎負責解析網站的 HTML 和CSS 檔案,並將之轉成顯示在螢幕上的畫素。

每個瀏覽器都有一渲染引擎。Chrome 的渲染引擎為 Blink,Edge 的叫EdgeHTML,Safari 的是 WebKit,Firefox 的引擎則名為 Gecko。

為了把檔案轉為畫素,所有的渲染引擎基本上都會做這些事:

  1. 將檔案剖析為瀏覽器可理解的物件,包括 DOM 在內。在這個階段,DOM 知道頁面的結構,也知道各元素之間的父/子關係,但不清楚這些元素應該是什麼模樣。

2.搞懂這些元素應該是什麼模樣。針對每個 DOM 節點,CSS 引擎都會一一確定哪些 CSS 規則適用,然後再為該 DOM 節點的每個 CSS 屬性設定一個值(value)。

3.確認每個節點的大小及其在螢幕上的位置,並為將在螢幕上顯示的每個東西生成方塊框(box,或做「盒子」)。這些方框不只代表 DOM 節點,DOM 節點內的東西也可有方框,如文本行。

4.繪製不同的盒子。這可能會有多層的情況,好比古早時代的手繪卡通,要用好幾層描圖紙來畫一樣。這樣就可以只改變一個層,而不必在其他層上重繪物件。

5.取得不同的繪圖層、運用只有合成器的屬性(如 transforms),並將它們變成一個圖像。這過程基本上就像是把各層疊在一起組合成圖片一般。之後,此圖像將被渲染在螢幕上。

也就是說,當 CSS 引擎開始計算樣式時,CSS 引擎手上有兩樣東西:

  • 一個 DOM 樹
  • 一份樣式規則的清單

它會逐一檢視每個 DOM 節點,並計算出各個 DOM 節點的樣式。在此過程中,它還會為 DOM 節點的各 CSS 屬性提供值(即使樣式表並未提供該屬性的值)。

我覺得這就像有一個人要填表格一樣。他必須幫每個 DOM 節點填一份表格,且表格上的所有欄位都不能留白。

要填好所有的表格,CSS 引擎必須做兩件事:

  • 找出哪些規則適用於哪些節點(也就是要做選擇器配對,selector matching)
  • 使用父(parent)級或預設值來填補缺少的值(即所謂的串接,cascade)

選擇器配對(Selector matching)

針對這個步驟,我們將把與 DOM 節點匹配的所有規則都加入清單。因為多個規則可達成配對,所以同一屬性可能會有多個宣告(declarations)。

此外,瀏覽器本身還會加入一些預設的 CSS(名為用戶代理樣式表)。CSS 引擎怎麼判斷該選哪個值呢?

這就是明確性(specificity)規則派上用場的地方。基本上,CSS 引擎基本上會產生一個試算表,並將宣告排入不同的列中。

具最高明確性的規則勝出。因此,根據此試算表,CSS 引擎會先填入它能填的值。

至於其他的部分,我們將使用到串接功能。

串接(cascade)

串接把 CSS 變得易於編寫和維護。透過串接,你可以設定 body 的顏色屬性,並知道 p 的文本、span 和 li 元素都會使用到該顏色(除非你有一個更明確的覆蓋(override))。

為此,CSS 引擎會檢視其表單上的空白框。如果其屬性為預設繼承,CSS 引擎便會往樹上方查看、確認是否其中一個祖先(ancestor)有值。如果沒有一個祖先有值、或該屬性不被繼承,CSS 引擎便將給予一個預設值。

到這裡,這個 DOM 節點的所有樣式都已計算完成。

補充說明:樣式結構共享

我剛給你看的表格有點不太真實。CSS 有數百種不同的屬性。如果 CSS 引擎為每個 DOM 節點的每個屬性都保留一個值的話,它應該會馬上用光記憶體。

實際的狀況是,引擎通常會進行所謂的樣式結構共享(style struct sharing)。它們把經常一塊出現的資料(如字體屬性)儲存在一個不同的物件中(即樣式結構),於是便不必保存一個物件內的所有屬性,而只要幫計算過的樣式物件提供指標(pointer)。如此一來,每個類別都會有一個指向具該 DOM 節點正確值之樣式結構的指標。

這可節省記憶體和時間。具類似屬性的節點(如兄弟姊妹般)只需指向同一結構,便可取得共享的屬性。因為許多屬性都是繼承而來,所以一個祖先元素便可與沒有指定其覆蓋之任一後代元素分享結構。

原文連結

--

--

Mozilla Taiwan

我們是Mozilla 美商謀智台灣分公司,由非營利組織 Mozilla 基金會所擁有,在台灣為自由開放的網路未來而努力。