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。

 

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