浏览器零散知识小汇总

526 阅读18分钟

一、浏览器进程

浏览器的进程

主要有:

  • Broswer进程:负责主控和协调,创建和销毁其它进程;控制下载;绘制位图等。
  • GPU进程:用于3D绘制;同一时刻最多拥有一个GPU进程
  • Render进程:处理页面渲染、脚本执行、事件处理等;一般来说一个标签页对于一个Render进程,当然也看具体浏览器的实现
  • 插件进程:处理插件相关工作。
  • 网络进程:处理网络请求相关工作。

Render进程的线程

Render进程是和前端密切相关的进程了,它拥有多个线程

  • GUI渲染线程(主线程):负责页面的构建,如解析HTML、CSS,构建布局树、图层树,生成绘制列表等
  • 合成线程:将图层分为图块,并把视口附近图块提交给光栅化线程
  • 光栅化线程(光栅线程池)::生成位图,即页面需要的每个像素点的颜色值
  • JS引擎线程:负责解析和执行js脚本,一个Render进程只有一个JS引擎线程,故常JS被称为单线程,而且该线程和GUI渲染线程是互斥的,这也是为什么JS中执行大量复杂计算容易导致页面渲染不连贯的原因
  • EventLoop轮询处理线程:控制事件循环;当JS引擎线程执行遇到像setTimeout这种异步执行代码块,会通知EventLoop轮询处理线程,之后由它进一步交付给对应异步线程并且等待触发条件成立时,又由它将回调函数加入相应处理队列,等待处理。
  • 定时器触发线程:负责定时器事件的计时,时间到了便会通知轮询处理线程将回调函数加入处理队列
  • 异步HTTP请求线程:XMLHttpReque在连接之后新启动的一个线程,该线程如果检测到请求的状态发生变更,如果设置有回调函数,会将回调函数加入事件队列。
  • 浏览器事件线程:浏览器事件线程会先监听dom,待到事件发生后通知轮询处理线程将回调函数加入处理队列

将进程结合页面渲染过程来看

前戏:

  1. 发送网络请求之后得到的响应数据由网络进程接收;并且解析响应头,成功则继续处理请求
  2. broswer进程根据响应头中的Content-type字段判断,如果为application/octet-stream,则启动下载进程;为text/html则启动渲染进程,broswer进程会发送一个“接收文档”消息给渲染进程;
  3. 渲染进程接收到“接收文档”的消息后,会和网络进程建立一个通道,接收数据。
  4. 渲染进程接收到数据后,向broswer进程发送“确认接收”消息
  5. 浏览器主进程接收到“确认接收”的消息后,开始更新浏览器页面,包括:地址栏的url,前进后退按钮。之后开始执行Render进程

正戏:

Render进程履行职责

  1. 渲染进程开始接受到数据的时候,会先预扫描接收到的数据,如果如果发现有需要加载资源的标签(img,link,外部script等),就先告诉浏览器主进程,先去下载,这个过程叫预解析
  2. 然后开始解析HTML
    • 遇到HTML标签则生成节点,构建DOM树
    • 遇到CSS代码则根据css的样式选择器构建CSSOM
    • 遇到CSS链接会继续下载
    • 遇到JS代码,会阻塞DOM构建,等待css代码下载完并解析完毕,然后再执行js代码
    • 遇到JS链接,如果是普通链接,则和上面一样;如果有async则等待JS下载完成,然后上面的流程;如果有defer,则会等到JS下载完毕且HTML解析完成再执行JS代码
  3. 之后根据DOM树和CSSOM构建布局树
  4. 构建完布局树,GUI线程还会对节点进行分层,构建图层树
  5. 构建好图层树后,GUI线程会为每一个图层创建绘制指令列表之后交由合成线程来处理
  6. 合成线程将图层分块,把浏览器用户视口附近的图块优先交给栅格化线程来生成位图;栅格化的过程通常会用GPU执行,就是说栅格化线程会把绘制图块的指令发送给GPU,然后GPU生成图块的位图(像素点的颜色值),存在GPU内存。
  7. 完成栅格化后,合成线程发出DrawQuad指令给broswer进程的VIZ组件
  8. VIZ组件将页面绘制到内存中,此时页面数据已经完成绘制
  9. 之后发给显卡处理;显卡分为前缓冲区后缓冲区,数据首先被送至后缓冲区,显卡将其合成为图片,之后前、后缓冲区对调,图片显示在屏幕上。

启发

  • CSS尽量放在前面,JS文件尽量放在后面,如果能够使用async和defer则尽量使用

  • 重绘会重新计算样式,跳过生成布局树和图层树,然后生成绘制列表及之后的步骤,而GUI线程和JS引擎线程互斥,因此很大程度上避免了两者的冲突;回流则重新构建布局树,对性能影响极大。

  • 位于合成层的元素如果发生改变,直接由合成线程处理,不占用GUI线程资源,即使GUI线程线程卡住,像动画效果这种仍然流畅显示

    因此,考虑到上面的这些情况:

    • 使用createDocumentFragment进行批量的DOM操作
    • 对于resize,监听滚动这种进行防抖/节流处理
    • 避免频繁修改样式,尽量使用改变class的方式一次性修改
    • 利用合成,使用CSS来实现动画效果,最好的方式是使用CSS的will-change属性
    • 避免使用过于复杂的属性选择器

WebWorker

  • 创建Webworker时,JS引擎向浏览器申请开一个子线程用于处理一个命名文件中的JS代码,它不能操作DOM,因为运行在另一个全局上下文中;可以通过postMessage和JS引擎线程通信,传递数据。所以耗时的计算操作可以通过Worker线程来执行,然后将结果返回给JS引擎线程;

  • SharedWorker是浏览器中的一个进程,所有页面共享;WebWorker线程,属于单个页面

二、浏览器缓存

1、HTTP缓存

强缓存

在发送HTTP请求之前,先检查强缓存,如果命中则直接返回。

如何检查强缓存?
  • HTTP/1.0通过Expires字段检查,该字段存在于服务器返回的响应头中
Expires: Wed, 22 Jun 2021 08:41:00 GMT

​ 表示资源在2021年6月22号8点41分过期

​ 存在的问题:服务器时间于客户端时间可能不一致,这导致过期时间不准确

  • HTTP/1.1采用Cache-Control字段,该字段使用过期时长来控制
Cache-Control: max-age = 3600

​ 表示资源在接收到响应后3600s内可以直接使用缓存

​ 它还有private(禁止代理缓存)、s-maxage(代理缓存过期时长)、no-cache(不使用强缓存,使用协商缓存)、no-store(禁止缓存)等字段

强缓存的使用
  1. 对于频繁变动的资源,显然使用强缓存不能保证每次获得最新的数据,因此建议设置Cache-Control字段为no-cache;在使用时会判断是否过期(即协商缓存)
  2. 对于不常变化的数据,可以将Cache-Control设置为一个大的max-age;如果后续有更新数据的需求可以在url之后添加hash、版本号、随机数等动态字符从而更改url,使得之前的强缓存失效
协商缓存

强缓存如果没有命中,客户端发送HTTP请求,并在请求头中携带相应的缓存tag来决定是否使用协商缓存

缓存tag
  • If-Modified-SinceLast-Modified

    首次请求时,服务器在响应体中加上Last-Modified字段告知客户端该资源的最后修改时间;客户端如果再次请求时,会设置If-Modified-Since值为之前服务器发回的Last-Modified这个值;服务器收到后对比If-Modified-Since和服务器端该资源当前Last-Modified,如果不一致,则资源过期,命中失败,否则说明资源在这段时间没有改变,服务器返回304告知客户端直接使用缓存。

  • ETagIf-None-Match

    ETag是服务器根据文件内容生成的一个标识符,如果文件内容改变则该值也会改变;浏览器收到该值后,会在下次请求中将这个值设置为If-None-Match字段的值;服务器接收到If-None-Match后,对比当前服务器中该资源的Etag值,如果不一致,说明资源发生改变;否则返回304;

  • 二者区别

    Etag是根据内容而定的,而Last-Modified关注的是是否进行过修改操作;用过word写过东西的人知道,如果你添加一个字之后又将其删除,它也会认为你进行了修改,即使内容没有发生实质上的改变,Last-Modified就是这样,它会记录该时间。

    因此Etag是更加精准的;还有就是Last-Modified的最小感知时间是1s,如果在发送给客户端Last-Modified后的1s中之内服务器端进行了更改,Last-Modified值不会变化;

    在性能上,Last-Modified是优于Etag

    如果两种方式都支持,服务器会优先考虑Etag

2、本地缓存

本地缓存属于浏览器内置的存储功能了,优点是可以通过JS进行控制,比HTTP缓存灵活

Cookie

一般由服务器生成,Cookie设计初衷就是用于弥补HTTP无状态的不足,它是一个很小的文本文件,4kb;内部以键值对的形式存储;Cookie与域名绑定(同一域名的一、二级是可以共享使用的),不可跨域;默认保存在与内存中,随着浏览器关闭消失,可以设置过期时间,设置了过期时间后会被保存在硬盘中,时间到了才消失

应用场景

  • 保存账号密码;
  • 保存上次登录的时间信息;
  • 保存上次查看的页面;
  • 浏览计数

缺陷:1、不管该域名下的某一地址需不需要,发送请求时都会带上Cookie,造成性能浪费;2、以纯文本形式在客户端和服务器之间传递,很容易被截获和篡改;在HttpOnly为false的情况下,Cookie还能被JS直接读取

WebStorage

HTML5引入了WebStorage,分为两种,二者皆采用键值对形式存储,值为字符串类型,因此传递对象之前先使用JSON.stringify处理

LocalStorage

同样是针对一个域名,容量上限为5MB,永久存储;只存在于客户端,避免了发送导致性能问题以及可能被截获的问题;封装的整体,通过其localStorage暴露的方法对其进行操作,方便。

应用场景
  • 内存容量大,持久,因此适合用于存储一些内容稳定,较大的资源如官网的logo;

  • localStorage还可以进行页面间传值;

  • 访问页面计数;

  • 由于Vuex刷新后会回到初始状态,因此可以在Vuex数据改变时将其存入local Storage,刷新页面后再取出;

操作方式:

let obj = {height:180 ,age: 18};
localStorage.setItem("name", "orange");
localStorage.setItem("info", JSON.stringigy(obj));//存储字符串类型

//在同一域名下可以拿到相应值
let name = localStorage.getItem("name");
let info = JSON.stringify(localStorage.getItem("info"));

更详细的实例

sessionStorage

容量上限5MB;只存储于客户端;封装,通过sessionStorage暴露方法;这些性质和localStorage几乎一样;但是sessionStorage于它的本质差别就是当会话结束,sessionStorage就消失了,它只临时存在于会话期间,它引入了一个"浏览器窗口概念",刷新和进入同源另一个页面,sessionStorage仍然存在,一旦关闭窗口就被删除了;两个窗口打开同一个页面它们的sessionStorage也是不同的。

应用场景
  • 用于存储表单信息,即使页面刷新也不会让之前的表单信息丢失,用于存储本次的浏览记录,如果页面关闭后就不再需要的话。
  • 可以单标签页同源页面之间传值
IndexDB

运行于浏览器的关系数据库,拥有数据库本身的特性;还有一些独特的特性:1、键值对存储 2、支持异步I/O操作 3、不可跨域访问数据库

三、XSS攻击

全程是Cross Site Scripting,即跨站脚本攻击,是web应用中的计算机安全漏洞,它允许恶意web用户植入代码到提供给其他用户使用的页面当中,本质是恶意代码未经过滤,与正常网站代码混合,无法被区分,导致浏览器执行恶意脚本。

一般可以完成下面这些操作:

  • 窃取cookie
  • 监听用户行为,如输入账号密码后盗取
  • 修改DOM伪造登录表单
  • 在页面中生成浮窗广告

XSS攻击的实现的三种方式:

1、存储型

攻击步骤:

  1. 攻击者将恶意代码提交至目标网站数据库中
  2. 用户打开目标网站,网站服务器将恶意代码从数据库取出,拼接到HTML,返回给浏览器。
  3. 浏览器解析HTML,执行恶意脚本
  4. 恶意脚本窃取用户数据或者冒充用户行为

常见的像是在评论区提交一段脚本代码,如果前后端没有做好转义工作,便存储到了服务器的数据库中,在页面渲染过程直接执行。

2、反射型

攻击步骤:

  1. 用户构造特殊URL,包含恶意代码,诱导用户点击
  2. 用户点击恶意URL,服务器将恶意代码从URL中取出,拼接到HTML上返回给客户端
  3. 浏览器解析HTML,执行恶意脚本
  4. 恶意脚本窃取用户数据或者冒充用户行为

反射性攻击常见于网站搜索,跳转等,通过诱导用户点击恶意链接,指的是恶意脚本作为网络请求的一部分,比如:

http://xxxx.com?s=<script>alert("hello")</script>

服务器拿到参数s,将其拼接到HTML上,然后返回给浏览器,浏览器将这些内容作为HTML内容进行解析,发现是个脚本,于是执行;

与存储型的区别:

存储型恶意代码存放在服务器,持久;反射性恶意代码存在于URL,非持久化

当然你可能会想,用户为什么要点击这些恶意URL,或者这些恶意URL是怎么到页面上的呢?

这其实有很多实现手段,比如结合ClickJacking(鼠标劫持);通过嵌入透明的iframe页面让你主动点击

3、DOM型

原因在于前端Javascript代码不够严谨,把不可以信任的数据当作代码执行; 比如使用innerHTML、outerHTML、document.write()插入了不可信数据

防范措施

针对XSS攻击两大要素:攻击者提交恶意代码;浏览器执行恶意代码

  1. 输入过滤:前端过滤:在用户提交时,由前端过滤输入,然后提交至后端;这样做的弊端是假如攻击者绕过前端,直接构造请求,就可以提交恶意代码了。后端过滤:后端在写入数据库前对输入进行过滤;弊端:由于后端数据可能提供给web前端和app客户端,如果后端采用escapeHTML()对输入内容进行编码过滤,返回给app客户端显示时就成了乱码。即后端无法明确内容最终输出到哪,而不同情况下采取的过滤方式还不同。

  2. 纯前端渲染:用于防止HTML注入。过程:浏览器先加载一个静态HTML,不包含任何业务相关数据,然后执行HTML中的Javascript,通过Ajax加载业务数据,调用DOM API更新页面。即明确的告知浏览器接下要设置的内容是文本还是样式等,浏览器不容易被欺骗去执行预期外的代码。

  3. 避免DOM型:谨慎使用innerHTML这些方法插入,尽量使用textContent,setAttribute这些方法。DOM的内联事件监听器,Javascript的eval,onclick,location,setTimeout等方法,a标签的href属性等都能直接执行字符串,应避免将不可信任的数据传递给这些API。

  4. 其它方案:利用CSP,即浏览器内容安全策略,它会1、限制其他域下的资源下载 2、禁止向其他域提交数据 3、提供上报机制,及时发现XSS攻击;利用Cookie的HttpOnly属性

四、CSRF攻击

攻击步骤如下:

  • 用户访问目标网站(a.com),并且保留登录凭证
  • 攻击者引诱用户点击链接,进入恶意网站(b.com)
  • 用户点击后,b.com发送请求如(a.com/act=xx)(可以通过图片或者script等来实现,也可以通过自动提交POST表单实现);浏览器会默认带上a.com的Cookie
  • a.com执行act=xx,攻击完成

全称Cross-site request forgery 即跨站请求伪造;指的是攻击者诱导目标点击自己的网站,然后利用用户目前的登录状态发起跨站请求。可能是下面这些情形:

1、自动发送GET请求

当你点击进入攻击者网站后,网站内可能有这样一段代码:

<img src="https://xxx.com/info?user=xxx%count=100">

一旦进入该页面,便会通过图片自动向xxx.com发送get请求,该请求会带上用户在xxx.com的Cookie信息(假定用户已经在xxx.com中登录过);如果服务器没有相应的验证机制,他可能认为发送请求的是正常用户,然后攻击者可以进行各种操作

2、自动发送POST请求

攻击者写了一个表单,并且进行提交,同样携带用户的Cookie信息,然服务器误认为是用户在操作

3、诱导点击

在攻击者的网站上可能有一些链接,诱导用户点击,这个和图片那个自动发送get请求差不多。

防范措施

  1. CSRF能够实现的原因携带用户的Cookie信息,伪装成用户进行操作。因此可以从Cookie上下手,Cookie的SameSite属性可以进行控制:1、设置为Stric,浏览器严格限制第三方携带Cookie 2、Lax,只能在get方法中提交表单或者通过a标签发送get请求时允许第三方携带cookie 3、None,默认模式,请求会自动带上Cookie;

  2. 也可以验证来源站点:通过验证请求头中OriginReferer两个字段,Origin只包含域名信息,Referer包含了具体路径;不过二者都可以伪造,在AJAX自定义请求头中修改即可,因此有效性较差

  3. 还可以通过CSRF Token,浏览器发送请求时,服务器会生成一个字符串,植入返回的页面中,之后浏览器发送请求必须带上这个字符串,通常第三方站点无法获得这个token

  4. 使用验证码,操作时需要用户输入验证码确认

五、标记清除算法和引用计数算法

标记清除算法:从根开始访问,标记所有可访问的对象;垃圾收集算法扫描并回收所有的未标记对象;

弊端:标记清除算法进行垃圾回收的时机是在内存耗尽时,程序被挂起,然后进行垃圾回收,显然这会造成页面在某个时间段内失去响应

好处:不会出现循环引用;对于引用对象的常规操作不会产生任何的额外开销。

引用计数:跟踪每个值被引用的次数;

弊端:循环引用

六、浏览器同源策略的作用?

同源策略限制:

  • 读取非同源的cookie,localStorage和indexDB
  • 获取非同源页面的DOM
  • 进行非同源Ajax请求

作用:

一 一对应上面:

  • 防止恶意网站通过js获取用户其他网站的cookie
  • 防止恶意网站通过iframe获取其它网站页面信息,操纵其他网站DOM,执行恶意操作
  • 防止恶意网站盗取用户在其它网站的信息

跨域实现

这个我就不多说了,很多博客总结的很清楚,这里推荐一篇播客吧:

场景跨域解决方案

References

浏览器基本工作原理

浏览器进程

浏览器缓存