讓 Chrome 崩潰的一行 CSS 程式碼

doodlewind發表於2019-03-03

一般的 CSS 程式碼只會出現 UI 版式或者相容性方面的小問題。但這裡我們要分享一行有趣的 CSS,它可以直接讓你的 Chrome 頁面掛掉 :)

復現

  1. 在 Chrome 裡開啟一個稍複雜的頁面,比如知乎或者掘金
  2. 開啟開發者工具,為頁面 <body> 增加樣式 style: "width:1px; height:1px; transform:scale(10000)"
  3. 欣賞工作管理員裡 Chrome 崩潰前的記憶體佔用

讓 Chrome 崩潰的一行 CSS 程式碼

其實這臺機器只有 8GB 記憶體,不過這不重要了。和讓 JS 崩潰的紅線容量 4GB 比起來,果然還是 CSS 更強大呢 :)

故事

這行程式碼的發現,源自於我們的編輯器專案在實現畫布尺寸調節時的一個詭異現象:使用者調節畫布尺寸時,只要新舊尺寸之比超過一定幅度,Chrome 就會卡死

雖然這個問題很難由普通使用者的操作路徑觸發,不過它所導致的後果確實比較嚴重。排查時我們首先考慮了 JS 阻塞和 DOM 重繪過頻等方面的可能性,但它們都不是問題所在。一個突破點在於偵錯程式 Rendering 工具中 FPS Meter 的輸出:

讓 Chrome 崩潰的一行 CSS 程式碼

這裡 GPU Memory 被佔滿了。雖然這個提示資訊現在看來很明顯是與硬體加速有關的,但在沒有相關經歷的情況下我們還是沒有確定它與具體程式碼之間的關聯。直到我們偶然檢視 Chrome 設計文件中關於 Compositing 的介紹時,發現了一個行為:Blink 會將 DOM 節點對映到 LayoutObject 的渲染樹,這棵樹中的節點理論上每個都能具備到渲染後端的上下文,但為了節約資源 Chrome 會將它們做一些合併後再渲染。而這時存在 CSS 定位(如絕對定位與 transform)的元素是不能合併的,這會造成對視訊記憶體的額外開銷。

基於這個資訊的提示,我們使用 Layout 工具來除錯當時的頁面,果然找到了一個特殊的地方:

讓 Chrome 崩潰的一行 CSS 程式碼

圖中最大的矩形 Layer 通過一般的 DOM 除錯是無法看見的,因此我們推測它的過大尺寸所導致的 RAM 開銷是罪魁禍首。基於這個資訊,我們最後找到了一個寬高都很合理但 transform 的 scale 值可能在邏輯中被修改得很大的 DOM 節點,限制它的 scale 上限即可解決問題:我們不難發現 scale 的值和最終對應畫素數量之間有著 O(N^2) 的關係,1 個畫素只放大 100 倍也有 10000 個畫素了。因此 scale 很大時對記憶體 / 視訊記憶體的過度使用也就是有可能的了(當然瀏覽器會做 Tiling 等工作,因此這不符合一般情況下的實際情形,Safari / Firefox 這時候也沒有出現問題)。最後給 Chrome 提了個 bug 見 #894115

總結

需要注意的是,因為缺乏對瀏覽器核心的深度瞭解,上面的除錯思路很可能是不準確的。簡單的總結:

  • 硬體加速是有代價的,最好能知道代價在哪
  • 瀏覽器的文件裡藏著很多有意思的東西
  • 除錯工具的一些冷門功能其實很強大,平時可以多試試

希望大佬指正,謝謝 XD

edit: 似乎不是所有裝置必現的,讓配置太好掛不掉的吃瓜同學們失望了。想更確定地復現的同學,在 bug 連結中有更容易復現問題的用例哈

edit-2: Chrome 團隊可以復現並 assign 了這個問題,見 #894115

相關文章