002 从URL请求到页面显示

9/16/2021 浏览器

# URL解析

web 应用的生命周期往往从用户输入一串url按下回车或点击一个链接开始,那么当我们输入内容后一般会有两种情况:

  1. 输入内容不符合url规则,那么此时浏览器进程会自动生成一个带关键字的url并自动跳转
  2. 输入内容符合url规则,自动添加协议头并进行跳转

值得注意的是跳转前 浏览器会执行 onbeforeunload 可用于提示用户,具体用法如下

// 用法1
<body onbeforeunload="return test()">
</body>
<script type="text/javascript">
    function test(){
        return "页面即将跳转";
    }
</script>
// 用法2
window.onbeforeunload=function(){
    return "页面即将跳转";
}
// 用法3 react
componentDidMount() {
  window.addEventListener("beforeunload", this.test);
}
componentWillUnmount() {
  window.removeEventListener("beforeunload", this.test);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 网络请求

  1. 查找缓存

    浏览器进程通过IPC将请求提交给网络进程,此时网络进程会开始查找强缓存,如果缓存在有效期内则直接返回。

  2. DNS解析

    通过DNS解析url获得具体请求IP地址,值得注意的是DNS同样有缓存,存在缓存则直接返回,如果不指定端口则添加默认端口,http默认端口为80,https端口为443

  3. 建立TCP连接

    通过三次握手建立TCP连接,注意:由于同源机制,chrome 只能建立不超过6个TCP连接(各家浏览器各不相同)。

    具体可以看这里 (opens new window)

  4. 建立TLS连接(使用 https 的情况下)

  5. 发送http请求

    http请求包含 请求行[方法、URL、协议]、请求头 Cookie 等、请求体 (get请求除外),这里不再赘述。

  6. 接收响应

    响应包含 响应行[协议、状态码、状态消息]、响应头、响应体等

    在众多 状态码 中我们需要特别注意的是 301(临时) / 302(永久) 这两个重定向状态码,当网络进程发现该状态码后,会在相应头中查找 location 字段,然后获取该字段的值直接进行跳转,此时尚未轮到 渲染进程 开始工作,所以从 js 的层面来说我们无法拦截301 / 302 状态,除了一种情况:响应头中没有location此时会直接进行下一步工作。

  7. 完成了网络请求和响应,如果响应头中Content-Type的值是text/html,那么接下来就是浏览器的解析渲染工作了

# 页面渲染

# 解析

  1. 构建DOM树

    由于浏览器无法理解HTML字符串,因此会将其转换为有意义且便于操作的数据结构,这种数据结构就是DOM树,在控制台输入document即可查看构建后的DOM树

  2. 样式计算

    由于浏览器同样无法识别CSS样式文本,所以同样会将其转换为一种结构化对象,机构化处理过程有属性值标准化,每个节点具体样式(继承、层叠),最后输出 styleSheet在控制台输入styleSheet 即可看到结构化后的CSS

  3. 生成布局树(DOM 树中元素的计划位置)

    现在已经生成了DOM树styleSheet,接下来则是计算可见元素的坐标位置,并生成一棵布局树(Layout Tree)

    • 遍历生成的DOM树节点,将可见元素添加到布局树中
    • 计算布局树各个节点的坐标位置

# 渲染

在构建完布局树之后并不能直接进入渲染流程,开始进入图层树生成

  1. 图层树生成

    • 1.1 渲染对象

      从浏览器的渲染过程中我们知道,页面 HTML 会被解析成 DOM 树,每个 HTML 元素对应了树结构上的一个 node 节点,每个节点都会对应一个渲染对象(RenderObject)

    • 2.1 渲染层

      渲染对象处于相同的坐标空间(Z轴坐标)时,都将归并到同一个渲染层中,渲染层将保证页面元素以正确的顺序堆叠,不同坐标空间的的渲染对象将形成多个渲染层,以体现它们的层叠关系。同时对于满足BFC条件的渲染对象,同样会创建新的渲染层。触发条件包括但不限于:

      • 根元素()
      • 有明确的定位属性(relative、fixed、sticky、absolute)
      • opacity < 1
      • 有 CSS fliter 属性
      • 有 CSS transform 属性且值不为 none
      • 有对于 opacity、transform、fliter、backdrop-filter 应用动画
      • overflow 不为 visible

      DOM 节点和渲染对象是一一对应的,满足以上条件的渲染对象就能拥有独立的渲染层。但并不代表着它们完全独享了渲染层,由于不满足上述条件的渲染对象将会与其第一个拥有渲染层的父元素共用同一个渲染层,因此实际上,这些渲染对象会与它的部分子元素共用这个渲染层。

    • 2.2 合成层

      满足某种条件的渲染层,将会被提升为合成层,硬件加速的关键点就在于 合成层将独享一个图形层(GraphicsLayer),而其他普通渲染层将与其第一个拥有图形层的渲染层共用一个。合成层的触发条件包括但不限于:

      • 3D transforms:translate3d、translateZ 等
      • video、canvas、iframe 等元素
      • 通过 СSS 动画实现的 opacity 动画转换
      • position: fixed
      • 具有 will-change 属性
      • animation 或者 transition 应用了 opacity、transform、fliter、backdropfilter
    • 2.3 图形层

      图形层是一个负责生成最终准备呈现的内容图形的层模型,它拥有一个图形上下文,图形上下文会负责输出该层的位图。存储在共享内存中的位图将作为纹理上传到 GPU,最后由 GPU 将多个位图进行合成,然后绘制到屏幕上

    • 3.1 利用合成层优化动画

      我们可以使用 will-change: transform 或者 transform: translateZ(0) 来生成一个新的合成层,从而将CPU消耗高的元素通过GPU加速来达到优化的目的

      同时也要注意避免隐式合成所导致的层爆炸

  2. 生成绘制列表

    接下来渲染引擎会将图层的绘制拆分成一个个绘制指令,如绘制背景、绘制边框、填充颜色等等,然后将指令按顺序组合成一个待绘制列表并提交给合成线程

  3. 光栅化

    绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的

    • 1 分块

      合成线程会将图层划分为图块(tile),这些图块的大小通常是 256x256 或者 512x512,然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的

    • 2 光栅化

      渲染进程中专门维护了一个栅格化线程池,专门负责把图块转换为位图数据,然后合成线程会选择视口附近的图块,把它交给栅格化线程池生成位图,生成位图的过程实际上都会使用 GPU 进行加速,生成的位图最后发送给合成线程

  4. 合成与显示

    一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程。浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。

Last Updated: 2/18/2022, 6:18:31 PM