浏览器基本工作原理(三)

220 阅读6分钟

浏览器内核

基于标记语言显示内容的程序或模块

渲染引擎(浏览器引擎)和 JS 引擎

渲染引擎

在浏览器窗口中显示所请求的内容,显示 html、xml 文档及图片,它也可以借助插件(一种浏览器扩展)显示其他类型数据

  • Trident:IE 浏览器
  • Gecko:Firefox 消耗很多的资源
  • Presto:Opera 兼容性
  • Webkit:Safari 前身是 KDE 小组的 KHTML 引擎,可以说 WebKit 是 KHTML 的一个开源的分支
  • Blink:谷歌 Webkit 的一个分支

渲染进程是如何工作的

  • 主线程 Main thread 处理您发送给用户的大部分代码
  • 工作线程 Worker thread 处理 WebWorker 或 ServiceWorker
  • 排版线程 Compositor thread
  • 光栅线程 Raster thread

渲染引擎基本流程.png

解析

构建 DOM

当渲染进程接收提交的导航消息和HTML数据,主线程开始解析文本串(HTML),使之成为一个DOM。解析中遇到 html 能优雅容错。

构建 DOM.png

  • 字节数据 => 字符串 => Token => Node => DOM

    • 字节数据 => 字符串

      浏览器都会去请求对应的 HTML 文件

    • 字符串 => Token

      浏览器会先将这些字符串通过词法分析转换为标记(token)

      代码分拆成一块块,并给这些内容打上标记

      标记.png

    • Token => Node

      标记会紧接着转换为 Node

      节点.png

子资源加载

网站通常使用图像,CSS 和 JavaScript 等外部资源,需要从网络或缓存加载。在解析构建DOM时,主线程可以逐个请求它们。为了加快速度预加载同时运行。

将 CSS 文件转换为 CSSOM 树

主线程解析 CSS 并确定每个 DOM 节点的计算样式,再根据 CSS 选择器将哪种样式应用于哪个元素。

字节数据 => 字符串 => Token => Node => CSSOM

  • 这一过程其实是很消耗资源,浏览器得递归 CSSOM 树,然后确定具体的元素到底是什么样式
  • 尽可能的避免写过于具体的 CSS 选择器
  • HTML 来说也尽量少的添加无意义标签

布局 - layout(回流 reflow)

渲染进程知道每个节点的文档结构和样式。布局是查找元素几何的过程。

布局过程主线程 遍历 DOM 并计算样式,并创建布局树(layout tree, 包含坐标和边界框大小等信息)。

生成渲染树.png

  • 渲染树只会包括需要显示的节点和这些节点的样式信息
  • 渲染树中的元素(WebKit 中称为「renderers」,Firefox 下为「frames」)与 DOM 元素相对应
  • 对于每个 DOM 元素,必须在所有 Style Rules 中找到符合的 selector 并将对应的规则进行合并

布局树的特殊情况

  • 不包括display: none的节点
  • 包括visibility: hidden的元素
  • 包括伪类元素,如::before

常见引起回流属性和方法.png

改变 window 大小,改变字体,添加或删除样式,文字改变,定位或者浮动,用户在 input 框中输入文字,盒模型,激活 CSS 伪类

  • 改变父节点里的子节点很可能会导致父节点的一系列回流

  • IE 中的 getComputedStyle(), 或 currentStyle

  • getBoundingClientRect()

  • Eventloop

    • 当 Eventloop 执行完 Microtasks 后,会判断 document 是否需要更新,因为浏览器是 60Hz 的刷新率,每 16.6ms 才会更新一次。
    • 然后判断是否有 resize 或者 scroll 事件,有的话会去触发事件,所以 resize 和 scroll 事件也是至少 16ms 才会触发一次,并且自带节流功能。
    • 判断是否触发了 media query
    • 更新动画并且发送事件
    • 判断是否有全屏操作事件
    • 执行 requestAnimationFrame 回调
    • 执行 IntersectionObserver 回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好
    • 更新界面
    • 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行 requestIdleCallback 回调

绘制 - Paint(重绘 repainting)

知道元素的大小,形状和位置,但是不知道绘制的顺序。主线程遍历布局树以创建绘制记录,绘制记录就是一个绘画过程。

常见引起重绘属性和方法.png

特点:

  • 布局树变化

    在每个步骤中,前一个操作的结果会影响创建绘制顺序。

  • 动画

    在每个帧之间运行这些操作。如果出现帧丢失或者帧之间运行 JS 时,则页面将出现卡顿。

    使用 setTimeout() 可能由于期间隔时间和屏幕刷新时间不同步,可能导致某些帧的操作被跨跃,直接更新下一帧的图像。

    将JS操作划分成小块,并安排在每个帧上运行 window.requestAnimationFrame(renderFunc)

    requestAnimationFrame 优点:

    • 省CPU时间:页面隐藏最小化时停止渲染,setTimeout持续运行

    • 函数节流:高频率事件(resize/scroll)为防止刷新间隔内多次执行函数,只执行一次更流畅

    • 将非dom操作的运行任务放在webWorker中运行,避免阻塞主线程

      // 任务文件 demo_workers.js
      var i = 0;
      
      function timedCount() {
        i = i + 1;
        postMessage(i); // 向页面发送数据
      }
      setInterval(timedCount, 500);
      
      // 包含dom的页面 index.html
      let w = new window.Worker("demo_workers.js"); // 开始
      w.onmessage = function (event) {
        document.getElementById("result").innerHTML = event.data;
      };
      
      w.terminate(); // 停止
      

合成

光栅化

浏览器知道文档的结构,每个元素的样式,页面的几何形状和绘制顺序,需将信息转换为屏幕上的像素,称为光栅化。

合成

合成是一种将页面的各个部分分层,分别栅格化,并在合成器线程的单独线程中合成为页面的技术。

如果发生滚动,图层已经被栅格化需要合成一个新帧。通过移动图层和合成新帧,可以以相同的方式实现动画。

分层

为了找出哪些元素需要在哪些层中,主线程遍历布局树以创建层树。如果页面的某些部分应该是单独的图层(如滑入式侧面菜单)但没有得到单独图层,可以使用CSS属性 will-change 提示浏览器。

Layer Tree.webp

过多的图层:过多图层合成可能会导致比页面小部分每帧光栅化更慢。

光栅和复合关闭主线程

  • 提交合成:创建层树并确定绘制顺序,主线程就会将该信息提交给合成器线程
  • 栅格化:合成器线程然后栅格化每个层,合成器线程将它们分成多个图块并将图块发送到栅格线程。
  • 栅格存储:栅格线程栅格化每个图块并将它们存储在GPU内存中。
  • 绘制四边形:一旦图块被光栅化,绘制四边形的图块信息(图块在内存中的位置、绘制图块页面中的位置)
  • 合成框架:合成器线程可以优先考虑视口(或附近)内的删格线程,以便优先被光栅化。图层还有不同分辨率的倾斜度,可以处理放大操作。
  • 创建合成器帧:收集绘制四边形的图块信息,通过IPC将合成器框架提交给浏览器进程
  • 浏览器UI合成:UI线程添加另一个合成器框架以用于浏览器UI更改,或者从其他渲染器进程添加扩展。
  • GPU展示:合成器帧被发送到GPU以在屏幕上显示。
  • 滚动事件:合成器线程会创建另一个合成器帧以发送到GPU

光栅和复合关闭主线程.webp

优点

  • 不涉及主线程的情况下完成
  • 合成线程不需要等待样式计算或JavaScript执行