浏览器地址栏输入url 到显示页面的步骤

1)在浏览器地址栏输入URL

2)浏览器查看缓存,如果资源未缓存,发起新请求,如果已缓存,检验是否过期,如果没过期就直接拿,否则发起请求。

3)发送请求的时候,先要知道服务器的IP地址,所以先进行域名解析。首先现在浏览器缓存里进行查找,然后是本地hosts文件。主机向本地DNS服务器查询,如果没有,本地域名服务器向其他根域名服务器继续发出查询请求报文,根域名服务器要么给出所要查询的 IP 地址,要么告诉本地域名服务器下一步向哪一个域名服务器进行查询。然后让本地域名服务器进行后续的查询。将查询结果告诉主机。

4)客户端通过三次握手和服务器建立TCP链接,

5)客户端向服务器发送HTTP请求报文。

6)服务器接受请求并处理完后返回一个HTTP响应报文。

7)浏览器接收HTTP响应后,客户端通过四次挥手和服务器断开连接。

8)把请求回来的HTML代码经过解析,构建成DOM树;

9)根据样式文件构建 CSSOM 树;

10)最后根据 CSSOM 树计算 CSS 属性并和 DOM 树构建渲染树;

11)根据渲染树计算每个节点的几何信息。

12)最后将各个节点绘制到屏幕上。

一、域名解析

域名解析的过程就是查找域名对应 IP 地址的过程。

浏览器先检查域名是否在缓存中,然后在本地hosts文件中寻找。然后主机向本地域名服务器查询,如果没有,本地域名服务器向其他根域名服务器继续发出查询请求报文,根域名服务器要么给出所要查询的 IP 地址,要么告诉本地域名服务器下一步向哪一个域名服务器进行查询。然后让本地域名服务器进行后续的查询。将查询结果告诉主机。

二、通讯

一旦获取到服务器IP地址,浏览器就会通过TCP”三次握手“与服务器建立连接。

浏览器向服务器发送HTTP请求报文。

服务器接受请求并处理完后返回一个HTTP响应报文。

浏览器接收HTTP响应后,浏览器通过四次挥手和服务器断开连接。

三、解析 HTML

1、词法分析

词法分析是将字符流解析成词(token)的过程。HTML 无法用常规的解析器进行解析,所以浏览器用自定义的解析器来解析 HTML,解析最常见的方案是使用状态机。

词法分析器(标记生成器):负责将输入内容分解成一个个有效标记。

1
2
3
4
5
6
7
<p class="a">text text text</p>
// 词(token)可以看成是这样的一个标记。
// <p“标签开始”的开始;
// class=“a” 属性;
// \> “标签开始”的结束;
// text text text 文本;
// </p>标签结束

关于状态机,初始状态“是数据状态”

  • 如果遇到一个非<字符,那么可以认为进入了一个文本节点;

  • 如果遇到字符 < 时,进入了一个标签状态。

    • 如果下一个字符是“ ! ” ,那么很可能是进入了注释节点或者CDATA节点。
    • 如果下一个字符是字母,那么可以确定进入了一个开始标签。这个状态会一直保持到接收 > 字符。在此期间接收的每个字符都会附加到新的标记名称上。
    • 如果下一个字符是 “/ ”,那么可以确定进入了一个结束标签。直到接收 >。然后将发送新的标记,并回到“数据状态”

用状态机做词法分析,其实正是把每个词的“特征字符”逐个拆开成独立状态,然后再把所有词的特征字符链合并起来,形成一个联通图结构。

2、语法分析(构建DOM树)

语法分析的过程就是构建DOM树过程。在创建解析器解析HTML代码的同时,也会创建 Document 对象。以 Document 为根节点的 DOM 树也会不断进行修改,向其中添加各种元素。词法分析生成的每个词(token)都会由树构建器进行处理。规范中定义了每个标记所对应的 DOM 元素,这些元素会在接收到相应的标记时创建。这些元素不仅会添加到 DOM 树中,还会添加到开放元素的堆栈中。此堆栈用于纠正嵌套错误和处理未关闭的标记。通过这个栈,我们可以构建DOM树:

  • 栈顶元素就是当前节点;
  • 遇到属性,就添加到当前节点;
  • 遇到文本节点,如果当前节点是文本节点,则跟文本节点合并,否则入栈成为当前节点的子节点;
  • 遇到注释节点,作为当前节点的子节点;
  • 遇到tag start就入栈一个节点,当前节点就是这个节点的父节点;
  • 遇到tag end就出栈一个节点(还可以检查是否匹配)。

解析器:负责根据语言的语法规则分析文档的结构,从而构建解析树。

3、浏览器的容错机制

在浏览 HTML 网页时从来不会看到“语法无效”的错误。这是因为浏览器会纠正任何无效内容,然后继续工作。

四、解析 CSS

在构建 DOM 树的过程中,如果遇到 link 的标签,当把它插到 DOM 树里面之后,就会根据 href 指明的链接去资源加载,这个加载是异步,不会影响 DOM 树的构建。CSS 解析需要经过词法分析和语法分析变成计算机能够理解的结构。

1、词法分析

WebKit 词法分析使用 Flex 将字符流解析成词(token)。

2、语法分析(构建CSSOM树)

WebKit 语法分析使用 Bison 解析器生成器,通过 CSS 语法文件自动创建解析器。解析器根据词(token)解析成 StyleSheet 对象,且每个对象都包含一个样式表里的多个 CSS 规则。一个CSS 规则对象则包含一个选择器和一个属性集。根据 document.styleSheets 拿到如下的 CSS 规则。最后把生成的 CSS 规则根据最右边的选择器把放到四个类型的哈希map中,构成 CSSOM。CSSOM 包括所有CSS选择器和每个选择器的相关属性的映射。

1
2
3
4
5
// 哈希 map 类型
CompactRuleMap m_idRules;
CompactRuleMap m_classRules;
CompactRuleMap m_tagRules;
CompactRuleMap m_shadowPseudoElementRules;

五、构建渲染树

1、渲染树的概念

在 DOM 树构建的同时,浏览器还会根据 DOM 树和 CSSOM 树一起构建渲染树。这是由可视化元素按照其显示顺序组成的树,也是文档的可视化表示。

Firefox 将渲染树中的元素称为“框架”,WebKit 使用的术语是渲染器或渲染对象。每一个渲染器都代表了一个矩形的区域,通常对应于相关节点的 CSS 框,框的类型会受到与节点相关的“display”样式属性的影响。特别的是如果一个元素需要创建特殊的渲染器,就会替换 createRenderer 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
RenderObject* RenderObject::createObject(Node* node, RenderStyle* style)
{
Document* doc = node->document();
RenderArena* arena = doc->renderArena();
...
RenderObject* o = 0;

switch (style->display()) {
case NONE:
break;
case INLINE:
o = new (arena) RenderInline(node);
break;
case BLOCK:
o = new (arena) RenderBlock(node);
break;
case INLINE_BLOCK:
o = new (arena) RenderBlock(node);
break;
case LIST_ITEM:
o = new (arena) RenderListItem(node);
break;
...
}
return o;
}

2、构建渲染树的过程

从 DOM 树的根节点开始深度优先遍历每个可见节点,根据该节点的选择器去 CSSOM 中计算该节点的样式,计算结果与该节点合并来构建渲染树。

这个过程的难点在于样式的计算,计算过程如下:

1)对每个节点按照id、class、伪元素、标签的顺序取出所有的选择器进行比较判断,最后是通配符,如果不匹配则直接返回;如果匹配,再判断当前选择器是不是最左边的选择器,如果是则返回匹配结果;否则再递归左边的选择器是否匹配。直到左边不匹配或没有时停止。找到节点对应的 CSS 规则后,就开始计算该节点具体的 CSS 属性的值。

2)因为可能会有多个选择器的样式命中相同节点,且浏览器本身也提供了一些默认的样式规则(用户代理样式表)。所以需要按照它们的优先级把样式属性综合在一起。如果属性默认为继承值,那么 CSS 引擎会沿着 DOM 树往上查找,看看其祖先元素是否设置了该值。

3)最后还会根据一些 CSS 属性值调整其他的 CSS 属性值。比如:把 absolute 定位、fixed 定位、float 的元素设置成 block。

渲染对象和 DOM 元素相对应,但并非一一对应。

1)非可视化的 DOM 元素不会插入渲染树中,例如“head”元素;如果元素的 display 属性值为“none”,那么也不会显示在渲染树中(但是 visibility 属性值为“hidden”的元素仍会显示)。

2)有一些 DOM 元素对应多个渲染对象。它们往往是具有复杂结构的元素,无法用单一的矩形来描述。

1
“select”元素有 3 个渲染对象:一个用于显示区域,一个用于下拉列表框,还有一个用于按钮。如果由于宽度不够,文本无法在一行中显示而分为多行,那么新的行也会作为新的渲染器而添加。

3)有一些渲染对象对应于 DOM 节点,但在树中所在的位置与 DOM 节点不同。浮动定位和绝对定位的元素就是这样,它们处于正常的流程之外,放置在树中的其他地方,并映射到真正的框架,而放在原位的是占位框架。

六、布局

渲染树创建完需要給每个节点添加位置和大小信息。计算这些值的过程称为布局或重排。

布局分全局布局和增量布局。全局布局是指触发了整个渲染树范围的布局(影响所有渲染器的全局样式更改,例如字体大小更改、屏幕大小调整)。增量布局是如果某个渲染器发生了更改,则对自身及其子代进行的布局(例如,当来自网络的额外内容添加到 DOM 树之后,新的呈现器附加到了呈现树中)。

HTML 采用基于流的布局模型,大多数情况下只要一次遍历就能计算出几何信息。处于流中靠后位置元素通常不会影响靠前位置元素的几何特征。

七、绘制

在绘制阶段,系统会遍历渲染树,将渲染器的内容显示在屏幕上。CSS2 规范定义了绘制流程的顺序。绘制的顺序是元素进入堆栈样式上下文的顺序。这些堆栈会从后往前绘制,因此这样的顺序会影响绘制。块呈现器的堆栈顺序如下:

  • 背景颜色
  • 背景图片
  • 边框
  • 子代
  • 轮廓

在重新绘制之前,WebKit 会将原来的矩形另存为一张位图,然后只绘制新旧矩形之间的差异部分。

Repaint(重绘):屏幕的一部分要重画,比如某个CSS的背景色变了。但是元素的几何尺寸没有变。

Reflow(重排):元件的几何尺寸改变了,我们需要重新计算渲染树。然后依次计算所有的结点几何尺寸和位置,最后将各个节点绘制到屏幕上。

参考链接

https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/#Parsing_general

https://developer.mozilla.org/zh-CN/docs/Web/Performance/How_browsers_work

https://zhuanlan.zhihu.com/p/25380611

https://segmentfault.com/a/1190000014520262