我是 Blink 配置團隊的工程部門主管 Ian Kilpatrick 和石井石西在加入 Blink 團隊前,我曾擔任前端工程師 (Google 尚未設立「前端工程師」一職),負責開發 Google 文件、雲端硬碟和 Gmail 的功能。在這個職位上待了五年後,我決定冒險轉換到 Blink 團隊,在工作中有效學習 C++,並嘗試掌握極為複雜的 Blink 程式碼庫。即使到今天,我只理解其中相對較小的一部分。感謝你在這段期間撥空回覆。 我很高興,因為許多「復健中的前端工程師」都比我更早轉換為「瀏覽器工程師」。
我先前在 Blink 團隊的經驗,也為我帶來許多啟發。身為前端工程師,我經常遇到瀏覽器不一致、效能問題、轉譯錯誤和缺少功能的問題。LayoutNG 讓我們有機會在 Blink 的版面配置系統中有系統地修正這些問題,並代表許多工程師多年來的努力成果。
在這篇文章中,我將說明這種大型架構變更如何減少及降低各種類型的錯誤和效能問題。
30,000 英尺的版面配置引擎架構
先前,Blink 的版面配置樹狀結構是我們稱為「可變動樹狀結構」的東西。
版面配置樹狀結構中的每個物件都包含輸入資訊,例如父項強制規定的可用大小、任何浮動項目的位置,以及輸出資訊,例如物件的最終寬度和高度,或其 x 和 y 位置。
這些物件會在渲染之間保留。當樣式發生變更時,我們會將該物件標示為骯髒,而且樹狀結構中的所有父項也相同。執行轉譯管道的版面配置階段時,我們會清理樹狀結構、檢查任何髒物物件,然後執行版面配置,讓這些物件處於清潔狀態。
我們發現這個架構造成許多問題 以下將加以說明不過,我們先來回顧一下版面配置的輸入和輸出內容。
在這個樹狀結構中的節點上執行版面配置,在概念上會採用「樣式加 DOM」,以及來自父項版面配置系統 (格狀、區塊或 Flex) 的任何父項限制,執行版面配置限制演算法,並產生結果。
我們新的架構將這個概念模型正式化。我們仍有版面配置樹狀結構,但主要用於保留版面配置的輸入和輸出。針對輸出,我們會產生一個全新的不可變物件,稱為「片段樹狀結構」。
我先前介紹了不變更的 Fragment 樹狀結構,說明其如何設計,以便重複使用先前樹狀結構的大部分內容,用於逐步變更版面配置。
此外,我們也會儲存產生該片段的父項限制條件物件。我們會將此做為快取索引鍵,我們會在下文進一步說明。
為了配合新的不可變動架構,系統也會重新編寫內嵌 (文字) 版面配置演算法。它不僅可為內嵌版面配置產生不可變動的平面清單表示法,還提供段落層級快取功能,可加快重新版面配置作業、每段落形狀,以便在元素和字詞之間套用字型功能,以及使用 ICU 的新 Unicode 雙向演算法、許多正確性修正等。
版面配置錯誤類型
大致而言,版面配置錯誤可分為四種,每種都有不同的根本原因。
正確性
當我們想到轉譯系統中的錯誤時,通常會想到正確性,例如:「瀏覽器 A 有 X 行為,而瀏覽器 B 有 Y 行為」,或是「瀏覽器 A 和 B 都出錯」。這項工作過去耗費了我們大量時間,而且過程中我們一直在與系統對抗。常見的失敗模式是針對某個錯誤進行非常精準的修正,但幾週後發現我們在系統的另一個 (看似不相關) 部分造成了回歸。
如先前的文章所述,這表示系統非常不穩定。就版面配置而言,我們沒有在任何類別之間擬定簡潔的合約,導致瀏覽器工程師依賴這些錯誤的狀態,或誤解系統其他部分的某些值。
例如,我們在一年中有超過 10 個與彈性版面配置相關的錯誤鏈,每項修正都會導致系統部分出現正確性或效能問題,進而導致另一個錯誤。
由於 LayoutNG 已明確定義版面配置系統中所有元件之間的合約,我們發現可以更有信心地套用變更。我們也從優異的 Web Platform Tests (WPT) 專案中獲益良多,這項專案可讓多方為共同的網路測試套件做出貢獻。
目前我們發現,如果在穩定版上發布真正的迴歸,通常在 WPT 存放區中通常沒有相關測試,也不會因對元件合約的誤解而產生。此外,根據我們的錯誤修正政策,我們一律會新增 WPT 測試,確保沒有瀏覽器會再次犯下相同錯誤。
撤銷不足
如果您曾經遇到奇怪的錯誤,在變更瀏覽器視窗大小或切換 CSS 屬性後,這個錯誤就會神奇地消失,那麼您就遇到了未充分驗證的問題。實際上,可變動樹狀結構的一部分被認為是乾淨,但由於父項限制有些許變更,這並不是正確的輸出內容。
這在下述的兩次掃描 (掃描版面配置樹狀結構兩次,以判斷最終版面配置狀態) 版面配置模式中十分常見。先前的程式碼如下所示:
if (/* some very complicated statement */) {
child->ForceLayout();
}
修正這類錯誤的做法通常如下:
if (/* some very complicated statement */ ||
/* another very complicated statement */) {
child->ForceLayout();
}
修正這類問題通常會導致嚴重的效能倒退 (請參閱下方的過度無效化),而且修正方式非常精細。
目前 (如上所述),我們有一個不可變動的父項約束條件物件,可描述從父項版面配置傳遞至子項的所有輸入內容。我們會將此內容儲存在產生的不可變片段中。 因此,我們會在集中位置比較這兩個輸入內容,判斷子項是否需要執行另一個版面配置階段。這個差異比較邏輯雖然複雜,但已妥善控管。針對這類未充分驗證的問題進行偵錯,通常會導致手動檢查兩個輸入內容,並決定輸入內容中哪些內容已變更,以便需要進行另一次版面配置傳遞作業。
由於建立這些獨立物件相當簡單,因此修正這個差異程式碼通常也相當簡單,而且可以輕鬆進行單元測試。
上述範例的差異比較程式碼如下:
if (width.IsPercent()) {
if (old_constraints.WidthPercentageSize()
!= new_constraints.WidthPercentageSize())
return kNeedsLayout;
}
if (height.IsPercent()) {
if (old_constraints.HeightPercentageSize()
!= new_constraints.HeightPercentageSize())
return kNeedsLayout;
}
催眠
這類錯誤類似於未充分驗證。基本上,在先前的系統中,要確保版面配置是冪等的,就是以相同的輸入內容重新執行版面配置,最後會產生相同的輸出內容。
在以下範例中,我們只是在兩個值之間來回切換 CSS 屬性。不過,這會導致「無限增長」的矩形。
在先前的可變樹狀結構中,很容易引入這類錯誤。如果程式碼在錯誤的時間或階段讀取物件的大小或位置 (例如,我們未「清除」先前的大小或位置),我們會立即新增微妙的滯後錯誤。這類錯誤通常不會出現在測試中,因為大多數的測試只著重在單一版面配置和轉譯。更令人擔心的是,我們知道需要部分這種滯後效應,才能讓某些版面配置模式正常運作。我們有錯誤,在執行最佳化作業以移除版面配置階段時,會引入「錯誤」,因為版面配置模式需要兩次階段才能取得正確的輸出內容。
透過 LayoutNG,由於我們具有明確的輸入和輸出資料結構,且不允許存取先前的狀態,我們可以廣泛減輕版面配置系統的錯誤類別。
過度無效化和效能
這與未充分驗證類別的錯誤完全相反。通常在修正無效錯誤的錯誤時,我們便會觸發效能驟降。
我們經常必須做出艱難的選擇,優先考量正確性而非效能。在下一節中,我們將深入探討如何緩解這類效能問題。
兩次掃描版面配置的興起和效能落差
Flex 和格狀版面配置代表網路版面配置的表現力有所轉變。不過,這些演算法與先前的區塊版面配置演算法截然不同。
區塊版面配置 (在大多數情況下) 只需要引擎對所有子項執行一次版面配置。這對效能來說非常有幫助,但最終無法達到網頁開發人員所需的效果。
舉例來說,您通常會希望所有子項的大小能擴大至最大的大小。為支援這項功能,父項版面配置 (Flex 或 GridLayout) 會執行測量傳遞作業,以判斷每個子項的大小,然後執行版面配置傳遞作業,將所有子項拉伸至此大小。這項行為是 Flex 和 GridLayout 的預設行為。
這些兩次傳遞版面配置在效能方面一開始是可接受的,因為使用者通常不會將這些版面配置巢狀化。不過,隨著內容越來越複雜,我們開始發現重大效能問題。如未快取測量階段的結果,版面配置樹狀結構會在其 measure 狀態和最終版面配置狀態之間發生衝突。
先前我們會嘗試在 Flex 和 GridLayout 版面配置中加入非常明確的快取,以對抗這類成效急降的情況。這項做法有效 (我們在 Flex 上取得了重大進展),但我們一直在與超出和未達無效的錯誤奮戰。
LayoutNG 可讓我們為版面配置的輸入和輸出內容建立明確的資料結構,此外,我們也已建構測量和版面配置階段的快取。這會將複雜度降回 O(n),讓網頁開發人員可預測線性效能。如果有版面配置採用三段式版面配置,我們也會直接快取傳遞。 這可能會為未來安全引入更進階的版面配置模式提供機會,這就是 RenderingNG 如何從根本上解鎖可擴充性的例子。在某些情況下,格狀版面配置可能需要三次版面配置,但目前這種情況極為罕見。
我們發現,開發人員在版面配置方面遇到效能問題時,通常是因為版面配置時間的指數型錯誤,而非管道版面配置階段的原始吞吐量。如果稍微漸進式變更 (一個元素變更單一 CSS 屬性) 會導致版面配置出現 50-100 毫秒,那麼這很可能是指數版面配置錯誤。
摘要說明
版面配置是一個非常複雜的領域,我們沒有討論了內嵌版面配置最佳化 (具體是整個內嵌和文字子系統的運作方式) 等各種有趣的細節,甚至是這裡談到的概念,都只是凸顯表面,也透露出許多細節。 不過,我們希望您能瞭解,系統架構的改善工作若能有系統地進行,長期下來就能帶來巨大的效益。
不過,我們知道還有許多工作要做。 我們知道有一系列問題 (包括效能和正確性) 正在解決中,也期待 CSS 推出新的版面配置功能。我們認為 LayoutNG 的架構可安全且可行地解決這些問題。
Una Kravets 拍攝的一張圖片 (你知道是哪張)。