• 1

  • 490

  • Favorite

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

黑猫

我不是黑客

1 month ago

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

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

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

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

全文参考inside look at modern web browser

1 chrome浏览器架构

1.1 相关概念

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

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

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

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

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

多进程架构

一个浏览器包括很多种任务,可以只使用一个进程包含多个线程,也可以多个进程,不同的浏览器会有不同的实现,总体来说还是多进程会更有优势一些,包括

  • 每个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相关工作

2 导航过程

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

  1. 当用户在地址栏输入一个地址时,ui首先判断是一次搜索还是一个url。
  2. 当用户点击回车,ui线程就会发起一个请求,此时tab上的loading图标显示加载中,network线程按照计算机网络介绍的过程建立连接并获取响应。请求一开始发起时,ui线程就知道了要加载一个页面因此会提前找一个renderer进程。如果收到的是重定向,network线程就会告诉ui线程,后者启动另一个请求。
  3. 当收到响应体时,network线程对其MIME Type sniffing,检查content-type和实际类型,如果收到的是html文件,则会交给renderer进程渲染,否则就会交给下载器下载。这时候也会进行安全检查,对于已知的恶意地址给予提示,阻止非法的跨域请求。
  4. 此时renderer进程和数据已经准备好了,browser进程发送一个ipc到renderer进程提交导航,并发送数据流。当browswe进程收到referer进程的确认,导航结束,进入文档加载和渲染阶段。此时地址栏更新,会话历史更新。
  5. 当renderer进程完成渲染(此时本页面和包含的所有iframes都完成onload),会回复一个ipc到browser进程,ui线程停止tab上的loading图标。
  6. 当在一个本来就渲染好的页面跳转为另一个地址,browser进程会先检查当前网站的beforeunload事件,如果能继续跳转则在后续渲染中使用另一个renderer进程。

3 文档加载和渲染过程

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

3.1 Parsing

renderer进程收到导航提交信息后开始接收html数据,并将其解析为dom,解析过程中遇到link或image标签会并行下载,并将css解析为cssom,但是遇到script标签时会停止解析dom,直到js下载、解析、运行结束,因为js可能会改变整个dom,相关优化不在这里展开,详见一开始提到的web性能文章。

3.2 Style calculation

当dom和cssom都创建结束,就开始进行样式计算,这步结束后为每个元素的个样式属性计算出一个Computed Values,可在devtools的computed部分查看。

3.3 Layout

在我们知道了单个元素的样式后,还需要知道每个元素的坐标,计算过程中会忽略display:none的元素,伪类等会包含在内,最后的结果是获得一个layout tree。

3.4 Paint

在得知了每个元素的坐标后还需要知道元素的绘制顺序,比如会受z-index属性的影响。这一步main线程会遍历layout tree创建paint records,后者是一个绘制顺序的笔记,可以参考canvas元素的绘制顺序进行理解。这一步很耗性能。

3.5 Compositing

将前面获取的数据绘制到屏幕上的步骤称为栅格化(rasterizing)
将页面分成不同单独的层被称为组合(compositing),负责组合的线程叫compositor线程。

前面步骤结束后要进行组合,为了计算出哪些元素在哪些层,main线程需要遍历layout tree创建layer tree,will-change属性可以影响这个步骤。

当layer tree被创建且绘制顺序确定,main线程将这些信息提交给compositor线程,后者开始对齐栅格化,每一层可能会很大,因此compositor线程将它们分成很多片段发给了raster线程,后者将每个片段交给各个gpu分别处理。其中靠近视口的部分会首先被处理,每一层可能有多个不同分辨率的片段提供给放大等操作。

当片段被栅格化后,compositor线程讲这些信息收集起来(这里被称为draw quads )创建一个compositor frame,这个compositor frame通过ipc提交给browser进程。这个frame和其他frame(比如来自其他rederer进程)一起发送到gpu展示在屏幕上。如果出现scroll事件,另外的frame就会按照上述步骤被gpu处理。

Compositing这里设计的步骤不会有main线程参与,因此不会引起layout或者paint的动画会很顺滑。

4 用户事件输入

这里的用户事件输入指的是所有和用户有关的事件交互(对事件想了解更多参考这里)。
当有事件触发时,browser进程会将事件类型和坐标发送给renderer进程,renderer进程会依此找出event target并执行绑定的event listeners,具体的执行需要main线程负责。其中event target的查找是通过paint records找出事件发生坐标下的元素。

当一个页面被组合时,compositor线程会将有event handlers绑定的区域标记为Non-Fast Scrollable Region,这个区域有事件输入时,compositor线程就会将事件发送给main线程处理,如果发生在其他区域,compositor线程就会自行生成一个新的frame而不等待main线程。
当body等元素被作为事件委托的对象时,整个页面就会成为Non-Fast Scrollable Region,因此任何事件都会通知main线程,为了解决这种不必要的通知,可在事件监听器中添加passive: true(参考EventTarget.addEventListener),这样即使有事件注册,compositor也可以先快速创建frame。


完结撒花

免责声明:文章版权归原作者所有,其内容与观点不代表Unitimes立场,亦不构成任何投资意见或建议。

信息安全

490

Relevant articles

未登录头像

No more data