Published on

浏览器基础知识

Authors

Preconditions:Chrome Version >= 72

多进程架构

1.Browser Process(浏览器进程):负责浏览器的TAB的前进、后退、地址栏、书签栏的工作和处理浏览器的一些不可见的底层操作

2.Renderer Process(渲染进程):一个Tab内的显示相关的工作,也称渲染引擎。

3.Network service process(网络进程):处理网络请求,从网上获取数据

4.Plugin Process(插件进程):网页使用到的插件

5.GPU Process(插件进程):整个应用程序的GPU任务

网页渲染原理

构建DOM

  • 主线程会解析数据转化为DOM(Document Object Model)对象。

资源子加载

  • 构建DOM的过程中,会解析到图片、CSS、JavaScript脚本等资源请求,逐一发起请求去获取
  • 为了效率,浏览器也会运行预加载扫描(preload scanner)程序,若HTML中存在img、link等标签,预加载扫描程序会把这些请求资源下载

JavaScript的下载与执行(重点 js引擎线程与GUI 渲染主线程互斥)

  • 遇到<script>标签,渲染引擎会停止对HTML的解析,而去加载执行JS代码,原因在于JS代码可能会改变DOM的结构(比如执行document.write()等API)
  • 如果在<script> 标签上添加了 asyncdefer 等属性,浏览器会异步的加载和执行JS代码,而不会阻塞渲染

样式计算 - Style calculation

  • 计算样式是主线程根据CSS样式选择器(CSS selectors)计算出的每个DOM元素应该具备的具体样式
  • 主线程在解析页面时,遇到<style>标签或者<link>标签的CSS资源,会加载CSS代码,根据CSS代码确定每个DOM节点的计算样式(computed style)

布局 - Layout

  • DOM树和计算样式完成后,需要知道节点在页面上的位置,布局(Layout)其实就是找到所有元素的几何关系的过程
  • 主线程会遍历DOM 及相关元素的计算样式,构建出包含每个元素的页面坐标信息及盒子模型大小的布局树(Render Tree),遍历过程中,会跳过隐藏的元素(display: none),另外,伪元素虽然在DOM上不可见,但是在布局树上是可见的

绘制 - Paint

  • 每个元素的绘制先后顺序
  • 在绘制阶段,主线程会遍历布局树(layout tree),生成一系列的绘画记录(paint records)绘画记录可以看做是记录各元素绘制先后顺序的笔记

合成 - Compositing

  • 文档结构、元素的样式、元素的几何关系、绘画顺序,这些信息我们都有了,这个时候如果要绘制一个页面,我们需要做的是把这些信息转化为显示器中的像素,这个转化的过程,叫做光栅化(rasterizing)
  • Chrome采取一种更加复杂的叫做合成(compositing)的做法。合成是一种将页面分成若干层,然后分别对它们进行光栅化,最后在一个单独的线程 - 合成线程(compositor thread)里面合并成一个页面的技术
  • 对元素进行分层,确定哪些元素需要放置在哪一层,主线程需要遍历渲染树来创建一棵层次树(Layer Tree)
  • 合成线程会收集图块上面叫做绘画四边形(draw quads)的信息来构建一个合成帧(compositor frame)。完成后合成线程就会通过IPC向浏览器进程(browser process)提交(commit)一个渲染帧。这个时候可能有另外一个合成帧被浏览器进程的UI线程(UI thread)提交以改变浏览器的UI。这些合成帧都会被发送给GPU从而展示在屏幕上。如果合成线程收到页面滚动的事件,合成线程会构建另外一个合成帧发送给GPU来更新页面。
  • 合成器线程可以独立于主线程之外通过已光栅化的层创建组合帧,例如页面滚动,如果没有对页面滚动绑定相关的事件,组合器线程可以独立于主线程创建组合帧,如果页面绑定了页面滚动事件,合成器线程会等待主线程进行事件处理后才会创建组合帧

浏览器对事件的处理

点击事件(click event)为例. 当鼠标点击页面时,首先接受到事件信息的是Browser Process,但是Browser Process只知道事件发生的类型和发生的位置,具体怎么对这个点击事件进行处理,还是由Tab内的Renderer Process进行。

渲染进程中合成器线程接收事件

  • 合成器线程可以独立于主线程之外通过已光栅化的层创建组合帧,例如页面滚动,如果没有对页面滚动绑定相关的事件,组合器线程可以独立于主线程创建组合帧,如果页面绑定页面滚动事件合成器线程会等待主线程进行事件处理后才会创建组合帧
  • 由于执行 JS 是主线程的工作,当页面合成时,合成器线程会标记页面中绑定有事件处理器的区域非快速滚动区域(non-fast scrollable region),如果事件发生在这些存在标注的区域,合成器线程会把事件信息发送给主线程,等待主线程进行事件处理,如果事件不是发生在这些区域,合成器线程则会直接合成新的帧而不用等到主线程的响应

查找事件的目标对象(event target)

  • 当合成器线程接收到事件信息,判定到事件发生不在非快速滚动区域后,合成器线程会向主线程发送这个事件信息,主线程获取到事件信息的第一件事就是通过命中测试(hit test)去找到事件的目标对象。具体的命中测试流程是遍历在绘制阶段生成的绘画记录(paint records)来找到包含了事件发生坐标上的元素对象

浏览器对事件的优化

  • 一般我们屏幕的帧率是每秒60帧,也就是60fps,但是某些事件触发的频率超过了这个数值,比如wheel,mousewheel,mousemove,pointermove,touchmove,这些连续性的事件一般每秒会触发60~120次,假如每一次触发事件都将事件发送到主线程处理,由于屏幕的刷新速率相对来说较低,这样使得主线程会触发过量的命中测试以及JS代码,使得性能有了没必要是损耗。
  • 出于优化的目的,浏览器会合并这些连续的事件,延迟到下一帧渲染时执行,也就是requestAnimationFrame之前
  • 非连续性的事件,如keydown,keyup,mousedown,mouseup,touchstart,touchend等,会直接派发给主线程去执行

Quoter:

下面几个链接的内容差不多