改善 Chromium 無障礙功能的效能

這篇文章是由 Chromium 貢獻者 Ahmed Elwasefi 撰寫,他分享了如何透過 Google 夏季程式碼計畫成為貢獻者,以及他發現並修正的無障礙性能問題。

在開羅德國大學就讀電腦工程的最後一年時,我決定尋找機會為開放原始碼做出貢獻。我開始探索 Chromium 的初學者友善問題清單,發現自己特別喜歡無障礙功能。我尋求指導時,認識了 Aaron Leventhal,他專業的知識和樂於提供協助的態度,讓我決定與他合作進行專案。這項合作成為我參與 Google 程式設計夏令營的經驗,當時我獲准與 Chromium 無障礙團隊合作。

在順利完成 Google 程式設計夏季挑戰後,我為了改善效能,繼續解決未解決的捲動問題。感謝 Google OpenCollective 計畫提供的兩筆補助金,讓我可以繼續進行這個專案,同時也能處理其他專注於簡化程式碼的任務,以提升效能。

這篇網誌文章將分享我在過去一年半以來與 Chromium 的合作歷程,並詳細說明我們所做的技術改善,特別是效能方面的改善。

無障礙程式碼對 Chrome 效能造成的影響

Chrome 的無障礙程式碼可協助螢幕閱讀器等輔助技術存取網頁。不過,啟用這項功能可能會影響載入時間、效能和電池續航力。因此,如果不需要,這個程式碼會保持停用狀態,以免影響效能。約 5-10% 的使用者啟用了無障礙程式碼,通常是因為密碼管理工具和防毒軟體等工具使用了平台無障礙 API。這些工具會使用這些 API 與網頁內容互動及修改內容,例如為密碼管理工具和表單填入工具找出密碼欄位。

目前尚未得知核心指標的總降幅,但最近一項名為「自動停用無障礙功能」的實驗 (在未使用時關閉無障礙功能) 顯示,降幅相當高。這個問題是因為 Chrome 無障礙程式碼庫的兩個主要領域 (轉譯器和瀏覽器) 有大量的運算和通訊作業,轉譯器會收集網頁內容和內容變更的相關資訊,並為節點樹狀結構計算無障礙屬性。任何髒節點都會經過序列化,並透過管道傳送至瀏覽器程序的主 UI 執行緒。這個執行緒會接收並非結構化的資訊,並將其轉換為相同的節點樹狀結構,最後再轉換為適合螢幕閱讀器等第三方輔助技術的適當表單。

改善 Chromium 無障礙功能

以下專案是在 Google OpenCollective 計畫資助下,於夏季程式碼期間和之後完成。

快取改善

Chrome 中有一種特殊資料結構,稱為「無障礙樹狀結構」,可鏡像 DOM 樹狀結構。這項屬性可協助輔助技術存取網頁內容。有時,當裝置需要此樹狀結構中的資訊時,可能還未準備就緒,因此瀏覽器必須將這些要求排程至稍後。

先前,這項排程作業是使用稱為關閉的函式處理,這項函式會將回呼放入佇列。由於關閉處理方式的關係,這個方法會增加額外的工作。

為改善這個問題,我們改用使用列舉的系統。系統會為每項工作指派特定的列舉值,當無障礙樹狀結構準備就緒後,就會呼叫該工作專用的正確方法。這項變更讓程式碼更易於理解,並提高了超過 20% 的效能。

執行階段效能測試圖表。
圖表顯示多項效能測試的執行時間,其中所有測試的執行時間都明顯下降約 20%。

找出並修正捲動效能問題

接下來,我將探討關閉邊界框序列化後的效能提升幅度。邊界框是網頁上元素的位置和大小,包括寬度、高度等詳細資料,以及相對於父項元素的位置。

為了測試這項功能,我們暫時移除了處理邊界框的程式碼,並執行效能測試,看看會產生什麼影響。其中一個測試 (focus-links.html) 的改善幅度高達 1618%。這項發現成為後續工作的基礎。

調查緩慢測試

我開始調查該測試在使用邊界框時速度緩慢的原因。這項測試只會依序將焦點放在多個連結上。因此,主要問題必須是將焦點放在元素上,或是在焦點動作發生時捲動畫面。為了測試這項功能,我在效能測試中將 {preventScroll: true} 新增至 focus() 呼叫,停止捲動。

在停用捲動功能後,啟用邊界框計算時,測試時間降至 1.2 毫秒。這表示捲動畫面才是真正的問題。

停用捲動功能的測試結果。
停用捲動或移除邊界框序列化後,焦點連結測試執行時間從 20 毫秒降至 1.1 毫秒。

我建立了名為 scroll-in-page.html 的新測試,用來複製 focus-links 測試,但這項測試不會使用焦點,而是會使用 scrollIntoView() 捲動元素。我測試了平滑和即時捲動,並且分別計算了邊界框架。

新測試的測試結果。
在即時捲動中,處理捲動所需的時間為 65 毫秒,而平順捲動則需要 123 毫秒。

結果顯示,使用即時捲動和定界框後,這個程序大約需要 66 毫秒。順暢捲動速度更慢,約為 124 毫秒。當我們關閉邊界框時,系統完全沒有花費任何時間,因為沒有觸發任何事件。

我們知道這個案例,但為何會發生這種情況?

我們現在知道,捲動是造成無障礙序列化速度緩慢的來源,但仍須找出原因。為了分析這個問題,我們使用了兩個名為 perfpprof 的工具,將瀏覽器程序中完成的工作分解開來。這些工具通常用於 C++ 的剖析作業。下列圖表顯示有趣的部分摘要。

剖析捲動測試的圖表。
從捲動測試的分析資料產生的圖表。顯示大部分時間都花在呼叫名為 Unserialize 的函式,以及另一個名為 IsChildOfLeaf 的函式。

經過調查,我們發現問題並非來自反序列化程式碼本身,而是該程式碼的呼叫頻率。為了瞭解這項問題,我們需要瞭解無障礙更新在 Chromium 中的運作方式。系統不會個別傳送更新,而是會在名為 AXObjectCache 的中央位置儲存所有資源。當節點發生變更時,各種方法會通知快取將該節點標示為「髒」,以便日後序列化。接著,所有未儲存的筆記屬性 (包括未變更的屬性) 都會序列化並傳送至瀏覽器。雖然這種設計可透過單一更新路徑簡化程式碼並降低複雜度,但在發生快速的「標示為髒」事件 (例如捲動時的事件) 時,速度就會變慢。唯一會變更的是 scrollXscrollY 值,但我們每次都會將其餘屬性與這些值序列化。更新率達到每秒二十次以上!

邊界框序列化會使用更快速的序列化路徑,只傳送邊界框詳細資料,讓您可以快速更新,而不影響其他屬性。這個方法可有效處理邊界框變更。

修正捲動問題

解決方案很清楚:使用邊界框序列化,納入目前的捲動偏移量。這可確保捲動更新作業透過快速路徑處理,提升效能,而不會造成不必要的延遲。我們將捲動偏移量與邊界框架資料一起封裝,藉此改善程序,讓更新過程更順暢、更有效率,為啟用無障礙功能的使用者提供更流暢的體驗。在捲動測試中,實作修正後的改善幅度最高可達 825%

程式碼簡化

在這段期間,我著重於程式碼品質,這是「Onion Soup」專案的一部分,該專案會減少或移除不必要地在各層中散布程式碼,以簡化程式碼。

第一個專案的目標是簡化無障礙資料從轉譯器序列化至瀏覽器的方式。先前,資料必須先經過額外層才能抵達目的地,這會造成不必要的複雜性。我們簡化了這項程序,讓資料可以直接傳送,省略中間的第三方。

此外,我們也找出並移除了導致系統產生不必要工作的過時事件,例如在版面配置完成時觸發的事件。我們已改用更有效率的解決方案。

也進行了其他小幅改善。很遺憾,這些項目並未記錄成效改善情形,但我們很高興地告訴您,程式碼比先前更清楚,且可自行記錄。這有助於日後改善成效。您可以查看我的 gerrit 個人資料,瞭解實際變更內容。

結論

與 Chromium 無障礙團隊合作,是一段非常有意義的旅程。透過解決各種挑戰 (從改善捲動效能到簡化程式碼庫),我對這類大型專案的開發工作有了更深入的瞭解,也學會了用於剖析的重要工具。此外,我瞭解到無障礙設計對於打造適合所有人的網站有多麼重要。我們所做的改善不僅能提升仰賴輔助技術的使用者體驗,也能提升瀏覽器的整體效能和效率。

成效結果相當出色。舉例來說,使用枚舉項目來排程工作可將效能提升 20%。此外,我們的捲動修正功能可讓捲動測試的時間減少最多 825%。程式碼簡化變更不僅讓程式碼更清晰且更易於維護,也為日後的強化作業鋪路。

我要向 Stefan Zager、Chris Harrelson 和 Mason Freed 表達感謝之意,感謝他們在這一年的支持和指導,特別是 Aaron Leventhal,沒有他就沒有這個機會。我也想感謝 Tab Atkins-Bittner 和 GSoC 團隊的支持。

如果您想參與有意義的專案並培養技能,我強烈建議您加入 Chromium 團隊。這是學習的好方法,而 Google 夏季程式碼等計畫則是您踏上這段旅程的絕佳起點。