chromium DOM 樹構建

    瀏覽器的整個過程,我借用李兵老師的一張簡化的流程圖如下,把瀏覽器的過程描述的很簡單易懂。瀏覽器線程負責用戶交互、文件儲存等功能,網絡線程面向渲染進程和瀏覽器進程等提供網絡下載功能,渲染線程主要職責是把從網絡下載的 HTML、JavaScript、CSS、圖片等資源解析爲可以顯示和交互的頁面,還有很多其他線程,慢慢理。瀏覽器線程是主線程,負責同用戶之間的交互,不能有耗時操作,所以網絡線程跟渲染線程是兩個獨立的線程。上一篇中介紹了network模塊,這裏開始整理下選染模塊的流程,會結合trace 看整個流程。

       之前跟蹤代內核代碼,都是到處找找文章,根據別人的整理的流程,添加日誌來看或者添加stacktrace來看,效率極低。有時候其實最怕的不是自己無知,而是不知道自己多無知,所以如果是看開源代碼,強烈建議看官網的文檔,當官網的文檔不能滿足你的要求的時候再去搜索其它的博客。像chromium的debug手段在其文檔中就有介紹,就像用trace能比很快的讓你對chromium的流程有初步的瞭解。然後再結合大神們總結的文檔,比較容易達到事半功倍的效果。推薦李兵老師在極客時間上的課程《瀏覽器工作原理與實踐》對瀏覽器原理總結的很清晰,可以先看看,再看瀏覽器內核代碼。

        用如下是我打開trace,加載淘寶網頁的trace截圖,左側列出了瀏覽器的線程,可以選擇你要關注的線程進行打開,鼠標左鍵左右拖動對其進行放大。

我選擇Chrome_InProcRendererThread線程放大後截圖入下圖,這樣就可以很方便的看整個流程,還可以通過Title 對應的搜索按鈕可以找到是哪個文件發送trace事件的。

好了前面簡單的介紹了下trace,然後接下來看看Render線程。

網絡線程負責資源的下載,其下載下來的HTML、CSS、JavaScript跟相關的資源文件,經過render線程,然後就變成屏幕上顯示的頁面了。看渲染線程前,建議補充下前端的一些基礎知識,什麼是html、css、JavaScript。

HTML(超文本標記語言)是用於在Internet上顯示Web頁面的主要標記語言。網頁由HTML組成,用於通過Web瀏覽器顯示文本,圖像或其他資源。HTML文件的文件擴展名爲.htm或.html

css層疊樣式表(CSS)是描述標記語言頁面格式的標準(或語言),CSS使開發人員能夠分離內容和可視元素,以實現更好的頁面控制和靈活性 CSS文件通常通過HTML文件中的鏈接附加到HTML文件

JavaScript是一種具有函數優先的輕量級,解釋型或即時編譯型的高級編程語言。可嵌入動態文本於HTML頁面,對瀏覽器事件做出響應,讀寫HTML元素等。

按照渲染的時間順序,渲染線程的流水線可分爲如下幾個子階段:構建 DOM 樹、樣式計算、佈局階段、分層、繪製、分塊、光柵化和合成。我們需要關注每個階段的輸入,處理過程,輸出,這樣就可以很清晰的瞭解渲染過程。

構建 DOM 樹:

DOM即文檔對象模型,是W3C制定的標準接口規範,是一種處理HTML和XML文件的標準API。DOM提供了對整個文檔的訪問模型,將文檔作爲一個樹形結構,樹的每個結點表示了一個HTML標籤或標籤內的文本項。DOM樹結構精確地描述了HTML文檔中標籤間的相互關聯性。將HTML或XML文檔轉化爲DOM樹的過程稱爲解析(parse)。HTML文檔被解析後,轉化爲DOM樹,因此對HTML文檔的處理可以通過對DOM樹的操作實現。DOM模型不僅描述了文檔的結構,還定義了結點對象的行爲,利用對象的方法和屬性,可以方便地訪問、修改、添加和刪除DOM樹的結點和內容。DOM 是保存在內存中樹狀結構

可以通過devtools,在console命令行中輸入document,可以看到DOM,幾乎跟HTML文件一樣,先對DOM有個直觀的瞭解,看看它是什麼樣的。

HTML解析過程如下圖所示,作圖是HTML輸入流,右側是解析後形成的DOM 樹:

下圖是chromium代碼實現HTML解析到構建DOM樹的過程。

(1)html_document_parser.cc將獲取一段段數據通過PostTask發送background_html_parser.cc,background_html_parser.cc解析成tokens,將相關的token傳給html_document_parser.cc。token包含哪些信息,可以去看atomic_html_token.h。注意這裏是獲取到一段數據後,而不是整個資源加載完成纔開始解析。如下是我打印的比較關注的token的name跟value。關注的函數:

HTMLDocumentParser::AppendBytes、BackgroundHTMLParser::AppendRawBytesFromMainThread、BackgroundHTMLParser::PumpTokenizer(這裏標記了需要預加載的鏈接標籤),

 BackgroundHTMLParser::EnqueueTokenizedChunk、HTMLDocumentParser::EnqueueTokenizedChunk(將預加載標籤放入隊列,空閒時進行加載)

html:

token

(2)解析成token後,html_document_parser.cc通過HTMLDocumentParser::ConstructTreeFromCompactHTMLToken、HTMLDocumentParser::ConstructTreeFromHTMLToken將token 傳遞給html_tree_builder.cc進行處理。通過調用HTMLTreeBuilder::ProcessToken進而調用到html_construction_site.cc的相關接口如:HTMLConstructionSite::InsertHTMLElement,這裏的傳遞還是token。在html_construction_site.cc中會根據傳入的token創建element元素,調用HTMLConstructionSite::AttachLater當前節點爲父節點,新建的element爲子節點,調用HTMLConstructionSite::QueueTask進行一個入棧操作。接着調用open_elements_.Push 由element跟token構建成HTMLStackItem傳遞給html_element_stack.cc進行入棧操作。通過HTMLElementStack::PushCommon將HTMLStackItem賦值給top_。出棧的話最終都會調用HTMLElementStack::PopCommon,先進後出。html_element_stack棧的深度限制是512,所以標籤的嵌套深度不得超過512。

HTML 解析器開始工作時,會默認創建了一個根爲 document 的空 DOM 結構,同時會將一個 StartTag document 的 Token 壓入棧底。然後經過分詞器解析出來的第一個 StartTag html Token 會被壓入到棧中,並創建一個 html 的 DOM 節點,添加到 document 上。我的理解是HTML 解析器獲取的數據流可以解析出多個token,根據token創建element,每個element爲一個個node,由node構成DOM tree。

 

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