前端需要理解的浏览器知识

23 篇文章 1 订阅
本文详细阐述了浏览器的多进程架构,特别是渲染进程的分工与协作,以及浏览器内核的发展历程。还介绍了浏览器渲染原理,包括HTML解析、样式计算、布局和合成过程,以及如何通过重绘、回流和合成优化渲染性能。此外,文章探讨了浏览器指纹技术,包括基本指纹和高级指纹,以及如何保护用户隐私。
摘要由CSDN通过智能技术生成

1 浏览器架构

浏览器是多进程多线程的应用程序,多进程可以避免相互影响和减少连环崩溃的几率:

  1. 浏览器(主)进程:主要负责界⾯显示、⽤户交互、⼦进程管理、存储等功能。内部会启动多个线程分别处理不同的任务。
  2. ⽹络进程:负责加载⽹络资源。⽹络进程内部会启动多个线程来处理不同的⽹络任务。之前是作为一个模块运行在浏览器进程内。
  3. 渲染进程(多个):渲染进程启动后,会开启⼀个渲染主线程,主线程负责执⾏ HTML、CSS、JS 代码。默认情况下,chrome浏览器会为每个标签开启⼀个新的渲染进程,以保证不同的标签⻚之间不相互影响(参见 Chrome 官方文档。处于安全考虑,渲染进程都是运行在沙箱模式下。
    1. 新增渲染进程
      • 新增标签页,无论同源与否;
      • 当前标签页内打开非同源页面;
    2. 复用渲染进程:当前标签页内打开同源页面;
  4. GPU进程:进行页面的绘制
  5. 插件进程(多个):负责插件的运行,因插件容易崩溃,需要单独的插件进行来隔离,避免插件运行崩溃对浏览器和页面造成影响。

2 浏览器内核

浏览器由外壳(shell)和内核组成,起初浏览器内核分为渲染引擎(layout engineer或Rendering Engine)和JS引擎,随着JS引擎独立化,内核就倾向于只指渲染引擎。

渲染引擎负责网页的效果显示和内容加载JS引擎负责解析和执行JavaScript完成动态效果和交互

常见的浏览器内核和五大浏览器:trident(IE)gecko(Firefox,扩展性和功能强大,内存消耗大)、 presto(opera,快,部分不兼容性,后来转向研发使用blink)、webkit( Safari,较快,代码容错性较低)、blink(Chrome,webkit精简强化版)、EdgeHTML(edge,现在也转向使用blink)

常见JS引擎有V8(Chrome)、SpiderMonkey(Firefox)、JavaScriptCore(Safari)。

3 浏览器渲染原理

准备好渲染进程后,浏览器进程接收到网络进程的响应头数据后,向渲染进程发起“提交文档”的消息,进入提交文档的阶段:

  1. 建立传输管道:渲染进程接收到浏览器进程发出的 “提交文档” 消息后,会和网络进程建立传输数据的 “管道”;
  2. 确认提交:等数据传输完成后,渲染进程会返回 “确认提交” 的消息给浏览器进程
  3. 更新浏览器界面状态:浏览器进程收到确认提交消息后,更新界面状态,包括安全状态、地址栏的URL、前进后台的历史状态以及进入渲染页面阶段

进入渲染页面阶段:产生一个渲染任务,并将其传递给渲染主线程的消息队列。在事件循环机制的作用下,渲染主线程取出消息队列中的渲染任务,开启渲染流程。

整个渲染流程分为多个阶段,分别是:HTML 解析样式计算布局分层生成绘制指令分块光栅化显示。每个阶段都有明确的输入输出,上一个阶段的输出会成为下一个阶段的输入。

解析 HTML

解析过程中遇到 CSS 解析 CSS,遇到 JS 执行 JS。为了提高解析效率,浏览器在开始解析前,会启动一个预解析的线程,率先下载HTML中的外部CSS文件和外部的JS文件。

如果渲染主线程解析到 <link> 位置,此时外部的 CSS 文件还没有下载解析好,主线程不会等待,继续解析后续的 HTML。这是因为下载和解析 CSS 的工作是在预解析线程中进行的。这就是 CSS 不会阻塞 HTML 解析的根本原因。同时,CSS文件的加载会阻塞后面的脚本JS的执行,因为脚本JS可能想要获取元素的坐标和其他与样式相关的属性。CSS的加载会阻塞DOM树的渲染。

如果主线程解析到 <script> 位置,会停止解析 HTML,转而等待 JS 文件下载好,并将全局代码解析执行完成后,才能继续解析 HTML。这是因为 JS 代码的执行过程可能会修改当前的 DOM 树,所以 DOM 树的生成必须暂停。这就是 JS 会阻塞 HTML 解析的根本原因JS的执行会阻塞DOM树的渲染

第一步完成后,会得到 DOM 树和 CSSOM 树,浏览器的默认样式、内部样式(<style>)、外部样式(<link>)、行内样式均会包含在 CSSOM 树中。

样式计算

主线程会遍历得到的 DOM 树,依次为树中的每个节点计算出它最终的样式,称之为 Computed Style。在这一过程中,很多预设值会变成绝对值,比如`red`会变成`rgb(255,0,0)`;相对单位会变成绝对单位,比如`em`会变成`px`这一步完成后,会得到一棵带有样式的 DOM 树。

布局

布局阶段会依次遍历 DOM 树的每一个节点,计算每个节点的几何信息。例如节点的宽高、相对包含块的位置。大部分时候,DOM 树和布局树并非一一对应(而且布局树里的对象是DOM对象不是同一个对象)。比如`display:none`的节点没有几何信息,因此不会生成到布局树;又比如使用了伪元素选择器,虽然 DOM 树中不存在这些伪元素节点,但它们拥有几何信息,所以会生成到布局树(或渲染树)中。还有匿名行盒、匿名块盒等等都会导致 DOM 树和布局树无法一一对应。布局完成后会得到布局树(或渲染树)

分层

主线程会使用一套复杂的策略对整个布局树中进行分层。分层的好处在于,将来某一个层改变后,仅会对该层进行后续处理,从而提升效率。<video> 、<canvas>、滚动条、堆叠上下文、transform、opacity 等样式都会或多或少的影响分层结果,也可以通过`will-change`属性更大程度的影响分层结果。

生成绘制指令

主线程会为每个层单独产生绘制指令集,用于描述这一层的内容该如何画出来。完成绘制后,主线程将每个图层的绘制信息提交给渲染进程的合成线程,剩余工作将由合成线程完成。

合成

合成线程首先对每个图层进行分块,将其划分为更多的小区域。它会从线程池中拿取多个线程来完成分块工作。分块完成后,进入光栅化阶段。

光栅化

合成线程会将块信息交给 GPU 进程,以极高的速度完成光栅化。GPU 进程会开启多个线程来完成光栅化,并且优先处理靠近视口区域的块。光栅化的结果,就是一块一块的位图。

显示(DrawQua)

合成线程拿到每个层、每个块的位图后,生成一个个「指引(Quad)」信息。指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑到旋转、缩放等变形。变形发生在合成线程,与渲染主线程无关,这就是`transform`效率高的本质原因。合成线程会把 Quad 提交给 GPU 进程,由GPU进程产生系统调用,提交给GPU硬件,完成最终的屏幕成像。

4 重绘、回流、合成

回流即重排(reflow),当渲染树中的一部分或者全部因为元素的尺寸、布局、隐藏等改变而需要重新构建的时候,这时候就会发生回流。

具体操作包括:

  1. DOM节点的尺寸,边距,填充内容,宽高改变;
  2. DOM节点display显示与否;
  3. DOM 节点的增删,位置改变;
  4. 浏览器窗口尺寸变化(resize);
  5. 读写 offset族、scroll族和client族和width,height属性时(浏览器为了获取这些值,需要进行回流操作);
  6. 调用 window.getComputedStyle(该方法返回指定元素的对象,通过对象的getPropertyValue方法获取指定css属性的最终计算值)和window.currentStyle 方法;
  7. 页面第一次渲染。

因此,对DOM的操作应该减少回流次数降低回流的规模节点数

reflow过程图:

reflow 的本质就是重新计算布局树。当进行了会影响布局树的操作后,需要重新计算布局树,会引发布局。相当于重新进行DOM的解析和合成,开销相当大。为了避免连续的多次操作导致布局树反复计算,浏览器默认会合并这些操作,当 JS 代码全部完成后再进行统一计算(但对于 window.getComputedStyle精确计算会强行刷新队列,无法优化),所以,改动属性造成的 reflow 是异步完成的。然而,如果JS 获取属性则浏览器会立即同步 reflow,否则当 JS 获取布局属性时,就可能造成无法获取到最新的布局信息。

重绘(repaint)是当修改导致了非几何属性的样式变化时触发,根据新的渲染树重新绘制改变的部分的过程。重绘过程(只计算样式和绘制列表

repaint 的本质就是重新根据分层信息计算了绘制指令。当改动了可见样式后,就需要重新计算,会引发 repaint。由于元素的布局信息也属于可见样式,所以回流一定会引起 重绘。重绘不一定回流

合成即在DOM的修改是CSS3 的transform、opacity、filter属性时触发。合成过程中,调用线程池完成分块,然后使用GPU(擅长处理位图数据)开启多个线程快速将块信息生成位图(光栅化),由于使用的是非主线程的合成线程,即使主线程卡住,也可以流畅的展示。

因此,可以利用重绘、回流、合成原理改进渲染过程

  1. 避免频繁使用 style,而是采用修改或添加class的方式;而对于确实需要动态修改多个style可使用element.style.cssText
  2. 使用createDocumentFragment文档碎片进行批量的 DOM 操作。
  3. 先display:none(不存在渲染树内),中间进行多个不可避免的回流操作,再display:block。
  4. 读写 offset族、scroll族和client族和width,height属性时尽量做变量缓存
  5. 涉及动画操作时,尽量绝对定位脱离文档流,来降低对父级元素回流影响
  6. 对于 resize、scroll 等进行防抖/节流处理(浏览器默认也会进行)。
  7. 添加 will-change: tranform ,让渲染引擎为其单独实现一个图层,当这些变换发生时,仅仅只是利用合成线程去处理这些变换,而不牵扯到主线程,大大提高渲染效率。当然这个变化不限于tranform, 任何可以实现合成效果的 CSS 属性都能用will-change来声明。 

5 浏览器指纹

浏览器指纹可以是UA,失去,地理位置或者使用的语言等,网站可以通过浏览器指纹获取到对应的使用者用户信息,能识别用户和记录用户的操作,进行个性化推荐。现有的浏览器指纹技术,由于目前跨浏览器识别指纹的问题尚未解决,可认为发展到处于2.5 代:

  1. 第一代是状态化的,主要集中在用户的 cookie 和 evercookie 上,需要用户登录才可以得到有效的信息。
  2. 第二代才有了浏览器指纹的概念,通过不断增加浏览器的特征值从而让用户更具有区分度,例如 UA、浏览器插件信息等
  3. 第三代是已经将目光放在人身上了,通过收集用户的行为、习惯来为用户建立特征值甚至模型,可以实现真正的追踪技术。但是目前实现比较复杂,依然在探索中。

浏览器指纹也分为基本指纹和高级指纹,由许多浏览器的特征信息综合起来的,不同特征值的信息熵(entropy,是接收的每条消息中包含的信息的平均量,信息熵越高,则能传输越多的信息,信息熵越低,则意味着传输的信息越少)有异。可以通过 Browserleaks 来检测浏览器指纹情况。FingerprintJS 是一个浏览器指纹识别库,它查询浏览器属性并从中计算哈希访问者标识符。

基本指纹就是容易被发现和修改的部分,比如:

经过运算,得到浏览器指纹具体的信息熵以及浏览器的 uuid,由于基本指纹的重复率较高,只能作为辅助识别,所以人们需要更精确的高级指纹来判断唯一性。甚至生成一个独一无二的跨浏览器身份。

高级指纹,比如像时区、屏幕分辨率和色深、Canvas、webGL 的信息熵在跨浏览器指纹上的权重是比较大的:

产生WebGL指纹原理是首先需要用着色器(shaders)绘制一个梯度对象,并将这个图片转换为Base64字符串。然后枚举 WebGL 所有的拓展和功能,并将他们添加到Base64字符串上,从而产生一个巨大的字符串,这个字符串在每台设备上可能是非常独特的。比如 fingerprintjs库的 WebGL 指纹生产。

浏览器指纹可能涉及到隐私泄露,如果不想被网站获取,是需要一些方法来阻止网站的。通过浏览器的扩展插件(Canvas Blocker、WebGL Fingerprint Defender、Fingerprint Spoofing等),在网页加载前执行一段 JS 代码,更改、重写 JS 的各个函数来阻止网站获取各种信息,或返回一个假的数据,以此来保护我们的隐私信息:

  1. 每个浏览器的UA
  2. 浏览器发送的 HTTP ACCEPT 标头
  3. 浏览器中安装的浏览器扩展/插件,例如 Quicktime,Flash,Java 或 Acrobat,以及这些插件的版本
  4. 计算机上安装的字体。
  5. 浏览器是否执行 JavaScript 脚本
  6. 浏览器是否能种下各种 cookie 和 “super cookies”
  7. 是否浏览器设置为“Do Not Track”
  8. 系统平台(例如 Win32、Linux x86)
  9. 系统语言(例如 cn、en-US)
  10. 浏览器是否支持触摸屏
  11. http 的 header
  12. Canvas 指纹:Canvas 是 HTML5 中的动态绘图标签,也可以用它生成图片或者处理图片。即便使用 Canvas 绘制相同的元素,但是由于系统的差别,字体渲染引擎不同,对抗锯齿、次像素渲染等算法也不同,Canvas 将同样的文字转成图片,得到的结果也是不同的。通过在网站上执行 Canvas 渲染代码,在画布上渲染一些文字,再用 toDataURL 转换出来,如此针对不同浏览器,Canvas 结果不尽相同。
  13. WebGL 指纹:WebGL(Web图形库)是一个 JavaScript API,可在任何兼容的 Web 浏览器中渲染高性能的交互式 3D 和 2D 图形,而无需使用插件。WebGL 通过引入一个与 OpenGL ES 2.0 非常一致的 API 来做到这一点,该 API 可以在 HTML5 元素中使用。这种一致性使 API 可以利用用户设备提供的硬件图形加速。网站可以利用 WebGL 来识别设备指纹,一般可以用两种方式来做到指纹生产:
    1. WebGL 报告——完整的 WebGL 浏览器报告表是可获取、可被检测的。在一些情况下,它会被转换成为哈希值以便更快地进行分析。
    2. WebGL 图像 ——渲染和转换为哈希值的隐藏 3D 图像。由于最终结果取决于进行计算的硬件设备,因此此方法会为设备及其驱动程序的不同组合生成唯一值。这种方式为不同的设备组合和驱动程序生成了唯一值。
  14. 混淆时区,就是更改 Date.prototype.getTimezoneOffset 的返回值。
  15. 混淆分辨率则是更改documentElement.clientHeight documentElement.clientWidth
  16. 混淆 WebGL 则要更改 WebGLbufferData getParameter方法等等。
  17. 混淆Canvas 指纹则需要更改 toDataURL 方法,比如 先使用 toDataURL() 将整个canvas的内容导出,通过 getImageData() 复制画布上指定矩形的像素数据并修改然后通过 putImageData() 将图像数据放回,然后再使用 toDataURL() 导出的图片,完成混淆。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

薛定谔的猫96

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值