rrweb 录制回放原理分析

最近在作 web 端录制 和回放的时候遇到一个比较好的库 rrweb, 可是网上的资料相对比较少,撸了遍源码,作个小总结。本文阅读大概会了解到如下几个方面~node

  • rrweb 是什么git

  • 适用场景github

  • 录制回放实现原理web

  • 使用 rrweb 应注意的问题数组

    • 数据量大的问题
  • 安全问题安全

  • rrweb 库组成部分 + 简介服务器

  • rrweb 各个模块对应的技术细节数据结构

1、rrweb 是什么?

web 端录制回放的一个基础库,即记录页面中的 DOM 结构还有用户操做行为,在远程实现回放。一图胜千言,可看下面的gif 图 app

rrweb 其实实现了 录制页面为用户的操做,回放页面则是根据数据回放用户的操做行为功能的一个库

2、适用场景

  • 记录⽤户使⽤产品的⽅式并加以分析,进⼀步优化产品。
  • 采集⽤户遇到 bug 的操做路径,予以复现。
  • 记录 CI 环境中的 E2E 测试的执⾏状况。
  • 录制体积更⼩、清晰度⽆损的产品演⽰。

3、录制回放基本原理

这一块能够简单地理解为 “快照 + 操做指令”, 通常记录页面状态,咱们能够根据时间记录每一时刻页面的 DOM状态,回放的时候根据时间点显示便可,可是通常一个页面DOM 数据是很庞大的,以下图所示,这个仍是页面 DOM 比较少的状况 异步

所以 rrweb 采起了另一种方式,记录初始页面的DOM 状态,或者特定某个时刻的DOM 状态,后续收集的是不一样时间点的操做指令 或者 某个时刻 某个DOM 的变化做为一个增量快照,在原先快照的基础上,不断加入根据行为解析的DOM 数据,构建了后续的快照,减小大量数据的存储或传输。

4、使用 rrweb 应注意的问题

  • 数据量大的问题
  • 数据安全问题 关于数据大的问题,其实rrweb 内部有作了一些处理,好比:
  • 根据DOM 变化或者有特定的用户操做行为时才收集数据;
  • 数据切片,分片不是单个数据的分片,而是如何把一天的数据或者是连续不刷新页面的数据进行分片,在rrweb里叫snapshot,好比每隔10分钟或者数据超过必定大小以后,进行一次数据分片,能够分开存储,这样在播放的时候,能够获取某一段的数据,不用播放一成天的。
  • 节流,对于鼠标移动,页面滚动事件进行了节流

除此之外,可本身对收集到的数据进行处理

  • 压缩数据,尝试过用 pako.js 进行压缩
  • 将数据切片保存在不一样的文件存储在云服务器,在播放的时候拉取文件整合数据再播放(这里只是一个想法,还没验证是否可行)

关于数据安全问题,这个应该加密便可

5、rrweb 组成部分

6、rrweb 各个模块对应的技术细节

一、rrweb-snapshot 提供了 snapshot, resbuid接口,snapshot遍历 页面DOM 返回当前页面 DOM 视图的一个序列化的数据结构, rebuild, 则解析特定的数据还原DOM, 并插入文档中 例如:

这个模块的功能主要有: a) snapshot 方法

  • 为每一个节点提供一个id,并在快照完成时返回id的节点映射
  • 相对路径的处理,将href,src,CSS中的相对路径设为绝对路径
  • 将页面引用的样式变为内联样式,以确保可使用本地样式
  • 将一些DOM状态内联到HTML属性中,例如HTMLInputElement的值
  • 将script标记转换为noscript标记,以免脚本被执行。

第一点:Snapshot 经过 takeFullSnapShotg构建页面 DOM 树,同时生成了 id -> Node 的映射,即在构建 DOM 树时为每一个节点生成一个惟一的id, 同时根据 id 生成一份映射,这个映射只要是为了方便后续的增量快照操做

第二点: 将href,src,CSS中的相对路径设为绝对路径 将一些脚本,样式,图片等引用的相对路径改成绝对路径 好比:

那通过转换以后,回放时,图片的连接地址已经变为以前域名下的地址

第三点: 将页面引用的样式变为内联样式,以确保可使用本地样式 将页面引用的样式读取变为内联样,例如

除此之外,还有一种样式逻辑,即经过 CssStyleSheet.sheet.insertRule的形式插入页面的样式:

第四点:将一些DOM状态内联到HTML属性中,例如HTMLInputElement的值

记录没有反映在 HTML 中的视图状态。例如 输⼊后的值不会反映在其 HTML中,咱们须要读取其 value 值并加以记录

第五点:将script标记转换为noscript标记,以免脚本被执行 在播放录制页面时,页面的脚本是不可以被执行的,须要禁掉

b) rebuild方法 经过建立Dom, 设置属性等,而且将对应的DOM 插入文档中

二、Rrweb 提供了 record 和 replay 功能。 a) record 方法: 前面说到增量快照,那增量数据是怎么收集的呢?开始录制以后,会针对当前页面生成一个DOM 快照,而后开始监听用户操做和页面DOM的变化。 监听行为以下:

  • DOM 变更
    • 节点建立、销毁
    • 节点属性变化
    • 文本变化
  • 鼠标移动
  • 鼠标交互
    • mouse up、mouse down
    • click、double click、context menu
    • focus、blur
    • touch start、touch move、touch end
  • 页面或元素滚动
  • 视窗大小改变
  • 输入

监听的方式有: (1)MutationObserver 其中,监听 DOM 变化 主要是经过 API --- MutationObserver 来实现。当监视的DOM 发生变更时, MutationObserver 将收到通知并触发预先设定好的回调参数,与 addEventListener 方法 比较类似 例如:

如上图所示,当咱们尝试改变页面 DOM 的属性,或者新增 DOM 节点的时候,都会对应生成一条 MutationObserver record, record 记录了一些变更信息~ 在 rrweb 中, 对每一条mutation record 作了几下处理

针对不一样的类型进行处理, characterData 是节点内容或节点文本变更,attributes 是节点属性的变更,childList 是子节点的变更,包括新增子节点,移除子节点,移动子节点等。

新增节点的逻辑 在初始记录时,生成了页面快照同时维护一个 id -> Node 的映射, 所以当出现新增节点时,无需从新完整地生成一份快照,而只须要将新节点序列化并加入映射中便可。 因为MutationObserver触发方式为批量异步回调,具体来讲就是会在一系列 DOM 变化发生以后将这些变化一次性回调,传出的是一个 mutation 记录数组,那在序列化的时候,会存在重复记录的问题 例如如下例子中

如下两种方式均可以生成这种DOM 结构

一、建立节点 n1 并 append 在 body 中,再建立节点 n2 并 append 在 n1 中。 二、建立节点 n一、n2,将 n2 append 在 n1 中,再将 n1 append 在 body 中。

MutationObserver 对这两种Dom 操做方式的输出记以下图所示

第一种方式 record 记录

这种状况下虽然 n1 append 时尚未子节点,可是因为上述的批量异步回调机制,当咱们处理 mutation 记录时获取到的 n1 是已经有子节点 n2 的状态

第二种方式 record 记录

在处理序列化的过程当中,在处理新增节点时必须遍历其全部子孙节点,才能保证全部新增节点都被记录,可是这一策略应用在第一种状况中就会致使 n2 被做为新增节点记录两次,回放时就会产生与原页面不一致的 DOM 结构,所以,为避免这种状况, rrweb 中采起了‘惰性’新增节点,即在遍历 mutation record 时,不会立马去序列化新增的节点,而是收集新增的节点,在遍历完mutation 全部记录的时候再统一去重,序列化新增节点。

删除节点的逻辑

一、 设置了 blocked 属性的话,不处理 二、 待移除节点还没被序列化,则说明是在本次 callback 中新增的节点,无需记录,而且重新增节点池中将其移除 三、 若是target在待移动节点集合中可是节点映射中又找不到这个子节点,说明子节点在mutation 回调以前已经被移除,target 在未来被序列化时是没有子节点的 四、 若是父节点已经被移除了,说明子节点也已经被移除了,不须要处理 五、 若是节点在待移动的节点结合中,则说明还没有序列化,从待移动的节点集合中删除便可 六、 以上条件都不符合时将节点添加入待移除节点集合中 七、 从 node 节点映射中去掉该节点的映射

(2)鼠标移动,鼠标交互,页面滚动,视窗大小这些则经过 事件绑定的形式去监听,以下面页面滚动的监听

on 方法也是经过 addEventListener 的形式监听

b) replay 解析收集到的events 集合,进行还原 收集到的事件类型有

  • DomContentLoaded
  • Load
  • FullSnapshot
  • IncrementalSnapshot
  • Meta 其中 当事件类型为 FullSnapshot时,会调用rebuild, 根据快照数据生成页面的DOM, 当事件类型为 IncrementalSnapshot 时,则说明是增量快照,即收集的数据只是DOM 的变化数据或者对应的用户行为数据,根据不一样的数据类型作对应的节点插入,删除,节点属性的更改等

7、写在最后

第一次阅读源码,写分析,可能比较粗糙,如有错误之处,欢迎指正。也欢迎一块儿交流,相关的问题也能够到github 上向做者提问哦,做者响应问题速度也是挺快的。

8、参考资料

zhuanlan.zhihu.com/p/60639266 github.com/rrweb-io

相关文章
相关标签/搜索