瀏覽器內核之 CSS 解釋器和樣式佈局

img

微信公衆號:愛寫bugger的阿拉斯加
如有問題或建議,請後臺留言,我會盡力解決你的問題。

前言

此文章是我最近在看的【WebKit 技術內幕】一書的一些理解和做的筆記。
而【WebKit 技術內幕】是基於 WebKit 的 Chromium 項目的講解。

書接上文 瀏覽器內核之 HTML 解釋器和 DOM 模型

本文剖析 WebKit 的 CSS 解釋器和樣式佈局。

從整個網頁的加載和渲染過程來看,CSS 解釋器和規則匹配處於 DOM 樹建立之後,RenderObject 樹建立之前,CSS 解釋器解釋後的結果會保存起來,然後 RenderObject 樹基於該結果來進行規範匹配和佈局計算。當網頁有用戶交互或者動畫等動作的時候,通過 CSSDOM 等技術,JavaScript 代碼同樣可以非常方便地修改 CSS 代碼,WebKit 此時需要重新解釋樣式並重復以上這一過程。

1. CSS 基本功能

1.1.1 簡介

CSS 的全稱是 Cascading Style Sheet,中文名是級聯樣式表,主要用來控制網頁的顯示風格。

1.1.2 樣式規則

圖 6-1 描述了一個典型的 CSS 規則結構。一個規則包括兩個部分——規則關和規則體。規則頭由一個或者多個選擇器組成;規則體則由一個或者多個樣式聲明組成,每個樣式聲明由樣式名和樣式值構成,表示這個規則對哪些樣式進行了規定和設置。

image.png

當 HTML 中的某個元素經過後面的匹配算法使用了這條規則,那麼將這些樣式設置成該元素的樣式,除非有更高優先級的規則匹配上該元素。

1.1.3 選擇器

CSS 的選擇器是一級模式,用來匹配相應的 HTML 元素。當選擇器匹配相應元素的時候,該選擇器包含的各種樣式值就會作用於匹配的元素上。通過選擇器,CSS 能夠精準地控制 HTML 頁面中的任意一個或者多個元素的樣式屬性。

具體的,這裏不做介紹,請查閱 CSS 規範。

1.1.4 框模型

框模型(Box model,或稱箱子模型)就是我們常說的盒子模型,是CSS 標準中引入來表示 HTML 標籤元素的佈局結構。一個框模型大致包括四個部分:外邊距(Margin)、邊框(Border)、內邊距(Padding)和內容(Content)。

image.png

1.4.5 包含塊(Containing Block)模型

當 WebKit 計算元素的箱子的位置和大小時,WebKit 需要計算該元素和另外一個矩形區域的相對位置,這個矩形區域稱爲該元素的包含塊。上面介紹的框模型就是在包含塊內計算和確定各個元素的,包含塊的具體定義如下:

  • 根元素的包含塊稱爲初始包含塊,通常它的大小就是可視區域(Viewport)的大小。

  • 對於其他位置屬性設置爲 “static” 或者 “relative” 的元素,它的包含塊就是最近祖先的箱子模型中的內容區域(Content)。

  • 如果元素的位置屬性爲 “fixed” ,那麼該元素的包含快脫離 HTML 文檔,因定在可視區域的某個特定位置。

  • 如果元素的位置屬性爲 “absolute” ,那麼該元素的包含塊由最近的含有屬性 “absolute”、“relative”、或者 “fixed” 的祖先決定,具體規則如下:如果一個元素具有 “inline” 屬性,那麼元素的包含塊是該祖先的第一個和最近一個 inline 框的內邊距的區域;否則,包含塊則是該祖先的內邊距所包圍的區域。

1.1.6 CSS 樣式屬性

CSS 標準中定義了各式各樣的樣式屬性,用來描述元素的顯示效果。
這些屬性大致分成以下類型:

  • 背景:如背景顏色和背景圖片等。

  • 文本:設置文本縮進,對齊。單詞間隔。字母間隔。字符轉換、裝飾和空白字符等。

  • 字體:設置字體屬性,可以是內嵌的,也可以是自定義字體的方式,另外還可以設置加粗、變形等屬性。

  • 列表:設置列表類型,可以以字母、希臘字母、數字等方式編號列表。

  • 表格:通過設置邊框來達到顯示錶格的視覺效果的目的。設置是否把表格邊框合併爲單一的邊框,設置分隔單元格邊框的距離,設置表格標題的位置,設置是否顯示錶格中的空單元格,設置顯示單元、行和列的算法等。

  • 定位:CSS 提供元素的相對、絕對定位和浮動定位。

1.1.7 CSSOM(CSS Object Model)

CSSOM 稱爲 CSS 對象模型。它思想是在 DOM 中的一些節點接口中,加入獲取和操作 CSS 屬性或者接口的 JavaScript 接口,因而 JavaScript 可以動態操作 CSS 樣式。DOM 提供了接口讓 JavaScript 修改 HTML 文檔,同理,CSSOM 提供了接口讓 JavaScript 獲得和修改 CSS 代碼設置的樣式信息。

對於內部和外部樣式表,CSSOM 定義了樣式表的接口,稱爲 “CSSStyleSheet”, 這是一個可以在 JavaScript 代碼中訪問的接口。藉助這個接口,開發者可以在 JavaScript 中獲取樣式表的各種信息,例如 CSS 的 “href”、樣式表類型 “type”、規則信息 “cssRules” 等,甚至可以獲取樣式表中的 CSS 規則列表。這個接口同 DOM 中的 “Script” 節點或者 “Link” 節點不一樣,它是 CSSOM 定義的新接口。開發者可以通過 document.styleSheets 查看當前網頁中包含的所有 CSS 樣式表,這是因爲 CSSOM 對 DOM 中的 Document 接口進行了擴展,下面是新加入的屬性:

image.png

W3C 還定義了另外一個規範是 CSSDOM View,它的基本含義是增加一些新的屬性到 Window、Document、Element、HTMLElement 和 MouseEvent 等接口,這些 CSS 的屬性能夠讓 JavaScript 獲取視圖信息,用於表示跟視圖相關的特徵,例如窗口大小,網頁滾動位移,元素的框位置、鼠標事件的座標等信息。下面是以 CSSDOM View 對 Window 的擴展:

image.png

1.2 CSS 解釋器和規則匹配

1.2.1 樣式的 WebKit 表示類

對於 CSS 樣式表,不管是內嵌還是外部文檔,WebKit 都使用 CSSStyleSheet 類來表示。圖 6-5 描述了 WebKit 內部是如何表示 CSS 文檔的。

image.png

一切的起源都是從 DOM 的 Document 類開始。

  • DoucmentStyleSheetCollection 類,該類包含了所在 CSS 樣式表

  • WebKit的內部表示類 CSSStyleSheet,它包含 CSS 的 href 、類型、內容等信息。

  • CSS 的內容就是樣式信息 StyleSheetContent,包含了一個樣式規則 (StyleRuleBase)列表。樣式規則被 用在 CSS 的解釋器的工作過程中。

下面部分 WebKit 主要是將解釋之後的規則組織起來,用於爲 DOM 中的元素匹配相應的規則,從而應用規則中的屬性值序列。這一過程的主要負責者是 StyleSheetResolver 類,它屬於 Document 類,幷包含了一個 DocumentRuleSets 類用來表示多個規則集合(RuleSet)。每個規則集合就是將之前解釋之後的結果合併起來,並進行分類,例如 id 類規則,標籤類規則等。至於爲什麼是多個規則集合,是因爲這些規則集合可能源自於默認的規則集合,或者網頁自定義的規則集合等。

1.2.2 解釋過程

CSS 解釋過程是指從 CSS 字符串經過 CSS 解釋器處理後變成渲染引擎內部規則的表示過程。

在 WebKit 中,過程如 6-8 所示。

image.png

這一過程是基本思想是由 CSSParser 類負責。CSSParser 類其實也是橋接類,實際的解釋工作是由 CSSGrammer.y.in 來完成。CSSGrammer.y.in 是Bison 的輸入文件,Bioson 是一個生成解釋器的工具。Bison 根據 CSSGrammer.y.in 生成 CSS 解釋器——CSSGrammer 類。當然 CSSGrammer 類需要調用 CSSParser類來處理解釋結果,例如需要使用 CSSParser 類創建選擇器對象、屬性、規則等。

在解釋網頁中自定義的 CSS 樣式之前,實際上 WebKit 渲染引擎會爲每個網頁設置一個默認的樣式,這決定了網頁所沒有設置的元素屬性及其屬性默認值和將要顯示的效果。一般來講,不同的 WebKit 移植可以設置不同的默認樣式。下面是 Chrome 瀏覽器使用的默認樣式,這些樣式決定了默認的網頁顯示效果。

image.png

1.2.4 樣式規則匹配

樣式規則建立完成之後,WebKit 保存規則結果在 DocumentRuleSets 對象類中。當 DOM 的節點建立之後,WebKit 會爲其中的一些節點(只限於可視節點)選擇合適的樣式信息。這些工作都是由 StyleResolver 來負責。當然,實際的匹配工作還是在 DocumentRuleSets 類中完成的。

圖 6-9 描述了參與樣式規則匹配的 WebKit 主要相關類。基本思路是使用 StyleResolver 類來爲 DOM 的元素節點匹配樣式。StyleResolver 類根據元素的信息,例如標籤名、類別等,從樣式規則中查找最匹配的規則,然後將樣式信息保存到新建的 RenderStyle 對象中。最後,最後這些 RenderStyle 對象被 RenderObject 類所管理和使用。

image.png

規則的匹配則是由 ElementRuleCollector 類來計算並獲得,它根據元素的屬性等,並從 DocumentRuleSets 類中獲取規則集合,依次按照 ID、類別、標籤等選擇器信息逐次匹配獲得元素的樣式。

image.png

首先,當 WebKit 需要爲 HTML 元素創建 RenderObject 類的時候,首先 StyleResolver 類負責獲取樣式信息,並返回 RenderStyle 對象,RenderStyle 對象包含了匹配完的結果樣式結果。

其次,根據實際需求,每個元素可能需要匹配不同來源的規則,依次是用戶代理(瀏覽器)規則集合、用戶規則集合和 HTML 網頁中包含的自定義規則集合。這三個規則的匹配方式是類似的。這裏是以自定義規則匹配爲例加以說明的。

再次,對於自定義規則集合,它先查找 ID 規則,檢查有無匹配的規則,之後依次檢查類型規則,標籤規則等,如果某個規則匹配上該元素,WebKit 把這些規則保存到匹配結果中。

最後,WebKit 對這些規則進行排序。對於該元素需要的樣式屬性,WebKit 選擇從高優先級規則中選取,並將樣式屬性值返回。

1.2.5 JavaScript 設置樣式

CSSDOM 定義了 JavaScript 訪問樣式的能力和方式。使用 CSSDOM 接口來更改屬性值的過程,在 WebKit 中,這需要 JavaScript 引擎和渲染引擎協同完成。

大致的過程是,JavaScript 引擎調用設置屬性值的公共處理函數,然後該函數調用屬性值解析函數,在這個例子中則是 CSS 的 JavaScript 綁定函數。而後 WebKit 將解釋後的信息設置到元素的 “style” 屬性的樣式 “webkitTransform” 中,然後設置標記表明該元素需要重新計算樣式,並觸發重新計算佈局。最後是 WebKit 的重新繪圖,圖 6-12 描述了其中的主要過程。

image.png

1.3 WebKit 佈局

1.3.1 基礎

當 WebKit 創建 RenderObject 對象之後,每個對象是不知道自己的位置、大小等信息的,WebKit 根據框模型來計算它們的位置,大小等信息的過程稱爲佈局計算。

第五章描述過 Frame 類,用於表示網頁的框結構,每個框都有一個 FrameView 類,用於表示框的視圖結構。

FrameView 類主要負責視圖方面的任務,例如網頁視圖大小,滾動、佈局計算、繪圖等,它是一個總入口類。 “layout” 和 “needsLayout” ,它們用來佈局計算和決定是否需要佈局計算,實際的佈局計算則是在 RenderObject 類中。

image.png

佈局計算根據其計算的範圍大致可以分爲兩類:第一類是對整個 RenderObject 樹進行的計算;第二類是對 RenderObject 樹中某個子樹的計算,常見於文本元素或者是 overflow:auto 塊的計算,這種情況一般是其子樹佈局的改變不會影響其周圍元素的佈局,因而不需要重新計算更大範圍內的佈局。

1.3.2 佈局計算

佈局計算是一個遞歸的過程,因爲一個節點的大小通常需要先計算它的子女節點的位置,大小等信息。

圖 6-14 描述了 RenderObject 節點計算佈局的主要過程,中間省略了很多判斷和步驟,主要邏輯都是由 RenderObject 類的 “layout” 函數來完成。

首先,該函數會判斷 RenderObject 節點是否需要重新計算,通常這需要通過檢查位數組中的相應標記位、子女是否需要計算佈局等來確定。

其次,該函數會確定網頁的寬度和垂直方向上的外邊距,這是因爲網頁通常是垂直方向上滾動,而水平方向儘量不需要滾動。

image.png

再次,該函數會遍歷其每一個子女節點,依次計算它們的佈局。每一個元素會實現自己的 “layout” 函數,根據特定的算法來計算該類型元素的佈局。如果頁面元素定義了自身的寬高,那麼 WebKit 按照定義的寬高來確定元素的大小,而對於像文本節點這樣的內聯元素則需要結合其字號大小及文字的多少等來確定其對應的寬高。如果頁面元素所確定的寬高超過了佈局容器包含塊所能提供的寬高,同時其 overflow 的屬性爲 visible 或 auto , WebKit 則會提供滾動條來保證可以顯示其所有內容。除非網頁定義了頁面元素的寬高,一般來說頁面元素的寬高是在佈局的時候通過相關計算得出來的。如果元素它有子女,則 WebKit 需要遞歸這一過程。

最後,節點根據它的子女們的大小計算得出自己的高度,整個過程結束。

重新佈局的情況:

首先,當網頁首次被打開的時候,瀏覽器設置網頁的可視區域(viewport),並調用計算佈局的方法。這其實也描述了一種常見的情景,就是當可視區域發生變化的時候,WebKit 都需要重新計算佈局,這是因爲網頁的包含塊的大小發生了改變。

其次,網頁的動畫會觸發佈局計算。當網頁顯示結束後,動畫可能改變樣式屬性,那麼 WebKit 就需要重新計算。

然後,JavaScript 代碼通過 CSSDOM 等直接修改樣式信息,它們也會觸發 WebKit 重新計算佈局。

最後,用戶的交互也會觸發佈局計算,例如翻滾網頁,這會角觸發新區域佈局計算。

CSS 的佈局計算是以包含塊和框模型爲基礎的,這表示這些元素的佈局計算都依賴於塊,例如 “div” 通常就是一個塊,如前面所述它們通常是在垂直方向上展開。

但是,CSS 標準也規定了行佈局形式,這就是內聯元素。內聯元素表現的是行佈局形式,就是說這些元素以行進行顯示。

以 “div” 元素爲例,如果設置屬性 “style” 爲 “displa: inline” 時,則該元素是內聯元素,那麼它可能與前面的元素在同一行。如果該元素沒有設置這個屬性時,則是塊元素,那麼在新的行裏顯示。這顯然會增加處理的複雜性,爲此,WebKit 的處理方式是 ——對於一個塊元素對應的 RenderObject 對象,它的子女要麼都是塊元素的 RenderObject 對象,要麼都是非內聯元素對應的 RenderObject 對象,這可以通過建立匿名塊(Anonymous Block)對象來實現。

佈局計算相對也是比較耗時間的,更糟糕的是,一旦佈局發生變化,WebKit 就需要後面的重新繪製操作。另一方面,減少樣式的變動而依賴現在 HTML5 的新功能可以有效地提高網頁的渲染效率。

總結

  • 匹配算法結合 CSS 規則來設置樣式

  • 選擇器就是選中某個元素的

  • 框模型就是常說的盒子模型,包含 margin、border、padding、content

  • CSSOM 稱爲 CSS 對象模型,JavaScript 可以獲取和操作 CSS 屬性。

  • CSS 解釋過程是指從 CSS 字符串經過 CSS 解釋器處理後變成渲染引擎內部規則的表示過程。

  • 當 WebKit 創建 RenderObject 對象之後,每個對象是不知道自己的位置、大小等信息的,WebKit 根據框模型(Frame 類的 FrameView)來計算它們的位置,大小等信息的過程稱爲佈局計算

  • 佈局計算是一個遞歸的過程,而且還會發生重新佈局。

最後

希望本文對你有點幫助。

下期分享 第七章 渲染基礎 敬請期待。

對 全棧開發 有興趣的朋友可以掃下方二維碼關注我的公衆號 —— 愛寫bugger的阿拉斯加

分享 web 開發相關的技術文章,熱點資源,全棧程序員的成長之路。

愛寫bugger的阿拉斯加

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章