常见js宿主环境(一):web browser

1,311 阅读5分钟

正如另一篇文章所言,ecma262只定义了语言层面的一些规范,js实际运行过程中还需要特定的宿主环境(host environment)提供输入输出等功能。

本系列介绍三种常见的js宿主环境

  • web browser(本文)
  • node.js
  • electron

本文以chrome为例,会对浏览器的架构以及相关运行过程做简要介绍,更多细节参考文中出现的链接和这篇web性能相关的文章

全文参考inside look at modern web browser

chrome浏览器架构

相关概念

在认识chrome具体架构之前我们先回顾几个概念

首先是两个比较重要的硬件

  • cpu 中央处理单元,是计算机的运算核控制中心,参考这里
  • gpu 即Graphics Processing Unit,可以理解为有很多核用于处理简单计算的cpu,一开始涉及为了处理图像,后来可以用来独立处理计算任务。

程序实际运行还涉及另外两个重要概念

  • 进程 为操作系统并发引入的概念,可以理解为应用中一个运行着的程序,参考这里
  • 线程 可以理解为轻量的线程,参考这里

多进程架构

一个浏览器包括很多种任务,可以只使用一个进程包含多个线程,也可以多个进程,不同的浏览器会有不同的实现(chrome浏览器为例,每个tab会分配一个渲染进程,其中的跨域iframe也会另外分配一个渲染进程,即Site Isolation),总体来说还是多进程会更有优势一些,包括

  • 每个tab分别处于不同进程,避免了一个tab无响应等对其他tab的影响
  • 增强安全性

本文介绍的架构(是参考文章发布时,即2018年9月,的设计)包括

  1. browser进程,浏览器的主进程,负责导航栏、书签、前进后退按钮,并且控制可见性以及特权功能,比如网络请求和文件访问。
    • ui线程 页面的展示和交互
    • network线程 网络请求
    • storage线程 存储
  2. renderer进程 控制一个tab内展示的部分,js engine和browser engine工作在这个进程。其中js engine,是es262的实现,用来运行js;browser engine,又叫做 layout engine or rendering engine,负责js引擎外其他工作,比如解析html和css,以及布局
    • worker线程
    • main线程 处理worker以外的代码
    • compositor线程
    • raster线程 后面两个线程参与渲染
  3. plugin进程
  4. gpu进程 负责其他进程中gpu相关工作

导航过程

这部分主要发生在browser进程,并涉及部分和renderer进程的交互。

处理输入

当在输入框输入内容时首先判断是查询还是访问url。

开始导航

当点击回车,ui线程启动一个network线程发起请求,loading spinner开始,如果返回的是重定向,会重新发起另一个请求

处理响应

当收到响应后会根据content-type等判断类型,如果是html就会发给renderer process,否则发给下载器。 这个阶段也会进行安全检查,包括cors

这里使用的renderer process会在前面发起请求时同时查找或者启动,如果重定向到一个跨域站点会重新重复这个动作

提交导航

当数据和renderer process都准备好后,browser进程会发送一个ipc renderer process来提交这个导航。一旦renderer 进程确认,导航完成,开始文档加载阶段。

此时地址栏和会话历史更新,

渲染结束

当当前界面的所有frames执行完onload事件,renderer进程就会发送给browser进程,loading spinner停止

6.导航到另一个站点 大部分和前面一样,开始之前需要触发beforeunload事件

3 文档加载和渲染过程

这部分任务是将html页面中的各种资源转化为可交互的页面。

Parsing

renderer进程收到导航提交信息后开始接收html数据,解析dom。

Style calculation

解析css,为每个元素属性添加一个computed style。

Layout

计算每个元素的坐标,生成layout tree,其中包含伪元素等。

Paint

遍历layout tree创建paint records,记录绘制顺序,比如z-index的影响。

Compositing

main线程遍历layout tree生成layer tree,确定每个元素在哪一层,然后将layer tree和paint records提交到compositor线程,后者将每一层分成小块发给raster线程分别栅格化然后保存在gpu内存中。 当所需的小块栅格化完成,compositor线程就用来生成compositor frame,然后通过ipc提交给browser 进程,然后发送到gpu显示在屏幕上。 如果滚动事件发生,如果当前页面没有绑定事件监听器,compositor线程就会创建其他compositor frame发送给gpu渲染新的页面,如果绑定事件监听器,看下一节。

用户事件输入

接收事件输入

当用户交互事件发生时,browser进程将事件的坐标和事件类型发送给renderer process

处理事件

compositor线程根据信息判断event target,如果对应区域有事件绑定(这样的区域被称为Non-Fast Scrollable Region),就要将事件发送到main线程执行,否则只需要进行必要的合成。

平滑滚动

当滚动事件触发时,conpositor线程每次composite new frame之前需要先和main 线程通信(因为可能会取消默认事件),进而造成卡顿,在监听器上采用{passive: true}可以直接合成新的帧,也不影响事件监听(这时候就不能取消默认行为)。

也可以直接取消事件绑定

#area {
  touch-action: pan-x;
}