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

相關文章
相關標籤/搜索