From b8cfc67d64dcd789f3cf03546fafa5b65523c782 Mon Sep 17 00:00:00 2001 From: davidl Date: Thu, 1 Jun 2023 18:42:47 +0800 Subject: [PATCH 1/3] Fix iframe shadowdomed canvas not being captured bug. A simple testcase would be an iframe containing canvas-datagrid. --- packages/rrweb-snapshot/tsconfig.json | 2 +- packages/rrweb/src/record/iframe-manager.ts | 6 ++- packages/rrweb/src/record/index.ts | 24 ++++++----- .../record/observers/canvas/canvas-manager.ts | 43 ++++++++++++++++--- .../rrweb/src/record/shadow-dom-manager.ts | 8 +++- packages/rrweb/tsconfig.json | 4 +- 6 files changed, 65 insertions(+), 22 deletions(-) diff --git a/packages/rrweb-snapshot/tsconfig.json b/packages/rrweb-snapshot/tsconfig.json index 879f459ae4..89781109e0 100644 --- a/packages/rrweb-snapshot/tsconfig.json +++ b/packages/rrweb-snapshot/tsconfig.json @@ -9,7 +9,7 @@ "preserveConstEnums": true, "rootDir": "src", "outDir": "build", - "lib": ["es6", "dom"] + "lib": ["es6", "dom", "ES2020", "ES2021.WeakRef"] }, "exclude": ["test"], "include": ["src"], diff --git a/packages/rrweb/src/record/iframe-manager.ts b/packages/rrweb/src/record/iframe-manager.ts index 377b7bc0ff..00bb5cea60 100644 --- a/packages/rrweb/src/record/iframe-manager.ts +++ b/packages/rrweb/src/record/iframe-manager.ts @@ -5,6 +5,7 @@ import CrossOriginIframeMirror from './cross-origin-iframe-mirror'; import { EventType, IncrementalSource } from '@rrweb/types'; import type { eventWithTime, mutationCallBack } from '@rrweb/types'; import type { StylesheetManager } from './stylesheet-manager'; +import type { CanvasManager } from './observers/canvas/canvas-manager'; export class IframeManager { private iframes: WeakMap = new WeakMap(); @@ -20,17 +21,19 @@ export class IframeManager { private loadListener?: (iframeEl: HTMLIFrameElement) => unknown; private stylesheetManager: StylesheetManager; private recordCrossOriginIframes: boolean; - + private canvasManager : CanvasManager; constructor(options: { mirror: Mirror; mutationCb: mutationCallBack; stylesheetManager: StylesheetManager; + canvasManager : CanvasManager; recordCrossOriginIframes: boolean; wrappedEmit: (e: eventWithTime, isCheckout?: boolean) => void; }) { this.mutationCb = options.mutationCb; this.wrappedEmit = options.wrappedEmit; this.stylesheetManager = options.stylesheetManager; + this.canvasManager = options.canvasManager; this.recordCrossOriginIframes = options.recordCrossOriginIframes; this.crossOriginIframeStyleMirror = new CrossOriginIframeMirror( this.stylesheetManager.styleMirror.generateId.bind( @@ -45,6 +48,7 @@ export class IframeManager { public addIframe(iframeEl: HTMLIFrameElement) { this.iframes.set(iframeEl, true); + this.canvasManager.addIFrame(iframeEl); if (iframeEl.contentWindow) this.crossOriginIframeMap.set(iframeEl.contentWindow, iframeEl); } diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts index c0e69a025f..0d03ecd5d8 100644 --- a/packages/rrweb/src/record/index.ts +++ b/packages/rrweb/src/record/index.ts @@ -282,9 +282,21 @@ function record( adoptedStyleSheetCb: wrappedAdoptedStyleSheetEmit, }); + canvasManager = new CanvasManager({ + recordCanvas, + mutationCb: wrappedCanvasMutationEmit, + win: window, + blockClass, + blockSelector, + mirror, + sampling: sampling.canvas, + dataURLOptions, + }); + const iframeManager = new IframeManager({ mirror, mutationCb: wrappedMutationEmit, + canvasManager: canvasManager, stylesheetManager: stylesheetManager, recordCrossOriginIframes, wrappedEmit, @@ -305,20 +317,10 @@ function record( const processedNodeManager = new ProcessedNodeManager(); - canvasManager = new CanvasManager({ - recordCanvas, - mutationCb: wrappedCanvasMutationEmit, - win: window, - blockClass, - blockSelector, - mirror, - sampling: sampling.canvas, - dataURLOptions, - }); - const shadowDomManager = new ShadowDomManager({ mutationCb: wrappedMutationEmit, scrollCb: wrappedScrollEmit, + canvasManager: canvasManager, bypassOptions: { blockClass, blockSelector, diff --git a/packages/rrweb/src/record/observers/canvas/canvas-manager.ts b/packages/rrweb/src/record/observers/canvas/canvas-manager.ts index ad6781dd58..df480ddc69 100644 --- a/packages/rrweb/src/record/observers/canvas/canvas-manager.ts +++ b/packages/rrweb/src/record/observers/canvas/canvas-manager.ts @@ -55,6 +55,17 @@ export class CanvasManager { this.locked = false; } + private shadowDoms = new Set>(); + private iframes = new Set>(); + + public addshadowRoot(shadowroot:ShadowRoot){ + this.shadowDoms.add(new WeakRef(shadowroot)); + } + + public addIFrame(iframeEl: HTMLIFrameElement){ + this.iframes.add(new WeakRef(iframeEl)); + } + constructor(options: { recordCanvas: boolean; mutationCb: canvasMutationCallback; @@ -157,14 +168,34 @@ export class CanvasManager { const timeBetweenSnapshots = 1000 / fps; let lastSnapshotTime = 0; let rafId: number; - + const that=this; const getCanvas = (): HTMLCanvasElement[] => { const matchedCanvas: HTMLCanvasElement[] = []; - win.document.querySelectorAll('canvas').forEach((canvas) => { - if (!isBlocked(canvas, blockClass, blockSelector, true)) { - matchedCanvas.push(canvas); - } + function collectWindowCanvas(win:Window){ + win.document.querySelectorAll('canvas').forEach((canvas:HTMLCanvasElement) => { + if (!isBlocked(canvas, blockClass, blockSelector, true)) { + matchedCanvas.push(canvas); + } + }); + } + // handle shadow root cases + that.shadowDoms.forEach(function(shadowroot:WeakRef){ + const sr=shadowroot.deref(); + if (!sr) return; + sr.querySelectorAll('canvas').forEach((canvas:HTMLCanvasElement) => { + if (!isBlocked(canvas, blockClass, blockSelector, true)) { + matchedCanvas.push(canvas); + } + }); }); + collectWindowCanvas(win); + // handle iframe cases + that.iframes.forEach((iframeEl:WeakRef)=>{ + const iframe=iframeEl.deref(); + if (!iframe) return; + if (!iframe.contentWindow || !iframe.contentDocument) return; + collectWindowCanvas(iframe.contentWindow); + }) return matchedCanvas; }; @@ -233,7 +264,7 @@ export class CanvasManager { ): void { this.startRAFTimestamping(); this.startPendingCanvasMutationFlusher(); - + const canvasContextReset = initCanvasContextObserver( win, blockClass, diff --git a/packages/rrweb/src/record/shadow-dom-manager.ts b/packages/rrweb/src/record/shadow-dom-manager.ts index 169c77216a..b5692a25c1 100644 --- a/packages/rrweb/src/record/shadow-dom-manager.ts +++ b/packages/rrweb/src/record/shadow-dom-manager.ts @@ -2,7 +2,7 @@ import type { MutationBufferParam } from '../types'; import type { mutationCallBack, scrollCallback, - SamplingStrategy, + SamplingStrategy } from '@rrweb/types'; import { initMutationObserver, @@ -12,7 +12,7 @@ import { import { patch, inDom } from '../utils'; import type { Mirror } from 'rrweb-snapshot'; import { isNativeShadowDom } from 'rrweb-snapshot'; - +import type { CanvasManager } from './observers/canvas/canvas-manager'; type BypassOptions = Omit< MutationBufferParam, 'doc' | 'mutationCb' | 'mirror' | 'shadowDomManager' @@ -27,10 +27,12 @@ export class ShadowDomManager { private bypassOptions: BypassOptions; private mirror: Mirror; private restoreHandlers: (() => void)[] = []; + private canvasManager: CanvasManager; constructor(options: { mutationCb: mutationCallBack; scrollCb: scrollCallback; + canvasManager : CanvasManager; bypassOptions: BypassOptions; mirror: Mirror; }) { @@ -38,6 +40,7 @@ export class ShadowDomManager { this.scrollCb = options.scrollCb; this.bypassOptions = options.bypassOptions; this.mirror = options.mirror; + this.canvasManager = options.canvasManager; this.init(); } @@ -52,6 +55,7 @@ export class ShadowDomManager { if (!isNativeShadowDom(shadowRoot)) return; if (this.shadowDoms.has(shadowRoot)) return; this.shadowDoms.add(shadowRoot); + this.canvasManager.addshadowRoot(shadowRoot); const observer = initMutationObserver( { ...this.bypassOptions, diff --git a/packages/rrweb/tsconfig.json b/packages/rrweb/tsconfig.json index fb1b407ad6..432266eb50 100644 --- a/packages/rrweb/tsconfig.json +++ b/packages/rrweb/tsconfig.json @@ -11,7 +11,9 @@ "outDir": "build", "lib": [ "es6", - "dom" + "dom", + "ES2020", + "ES2021.WeakRef" ], "downlevelIteration": true, "importsNotUsedAsValues": "error", From e5bb303f710a1f5069a8345fe7051f8c6cfa4030 Mon Sep 17 00:00:00 2001 From: davidl Date: Fri, 16 Jun 2023 10:39:12 +0800 Subject: [PATCH 2/3] add mouseCursor control in replayer. --- packages/rrweb/src/replay/index.ts | 9 ++++++--- packages/rrweb/src/types.ts | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/rrweb/src/replay/index.ts b/packages/rrweb/src/replay/index.ts index 1bc9c6a6a1..38579d029d 100644 --- a/packages/rrweb/src/replay/index.ts +++ b/packages/rrweb/src/replay/index.ts @@ -186,6 +186,7 @@ export class Replayer { triggerFocus: true, UNSAFE_replayCanvas: false, pauseAnimation: true, + mouseCursor: true, mouseTail: defaultMouseTailConfig, useVirtualDom: true, // Virtual-dom optimization is enabled by default. logger: console, @@ -569,9 +570,11 @@ export class Replayer { this.wrapper.classList.add('replayer-wrapper'); this.config.root.appendChild(this.wrapper); - this.mouse = document.createElement('div'); - this.mouse.classList.add('replayer-mouse'); - this.wrapper.appendChild(this.mouse); + if (this.config.mouseCursor !== false) { + this.mouse = document.createElement('div'); + this.mouse.classList.add('replayer-mouse'); + this.wrapper.appendChild(this.mouse); + } if (this.config.mouseTail !== false) { this.mouseTail = document.createElement('canvas'); diff --git a/packages/rrweb/src/types.ts b/packages/rrweb/src/types.ts index dd9a516709..4a0aeb8df6 100644 --- a/packages/rrweb/src/types.ts +++ b/packages/rrweb/src/types.ts @@ -172,6 +172,7 @@ export type playerConfig = { triggerFocus: boolean; UNSAFE_replayCanvas: boolean; pauseAnimation?: boolean; + mouseCursor:boolean; mouseTail: | boolean | { From 2df3f38b47ba7231a16e4788d4243d327617519a Mon Sep 17 00:00:00 2001 From: davidl Date: Fri, 16 Jun 2023 21:54:44 +0800 Subject: [PATCH 3/3] Make rrweb-player respect width,height props. --- packages/rrweb-player/src/main.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/rrweb-player/src/main.ts b/packages/rrweb-player/src/main.ts index df0568912d..8d50b4d5fe 100644 --- a/packages/rrweb-player/src/main.ts +++ b/packages/rrweb-player/src/main.ts @@ -3,6 +3,8 @@ import _Player from './Player.svelte'; type PlayerProps = { events: eventWithTime[]; + width: number; + height:number; }; class Player extends _Player {