From f86e608c8ac39dbbdc59f6f67f7fe0369c0044ff Mon Sep 17 00:00:00 2001 From: David Newell Date: Thu, 14 Mar 2024 16:54:56 +0000 Subject: [PATCH 1/2] fix: canvas recording patches --- patches/rrweb@2.0.0-alpha.11.patch | 432 ++++++++++++++++++++++++++++- pnpm-lock.yaml | 6 +- 2 files changed, 422 insertions(+), 16 deletions(-) diff --git a/patches/rrweb@2.0.0-alpha.11.patch b/patches/rrweb@2.0.0-alpha.11.patch index 4ab1e6da1..ee59a78d3 100644 --- a/patches/rrweb@2.0.0-alpha.11.patch +++ b/patches/rrweb@2.0.0-alpha.11.patch @@ -1133,19 +1133,425 @@ index 81ad4520d1435d088752a85f9a55a68ad6e0f514..e19dec6cc98ea782d53328853ca1b46d export { MutationBuffer as default }; diff --git a/es/rrweb/packages/rrweb/src/record/observers/canvas/canvas-manager.js b/es/rrweb/packages/rrweb/src/record/observers/canvas/canvas-manager.js -index 8746997c9b849ac5c952fdbe0a8dd608d6680a3a..534c6076f1cf63dfde972bb45991fc70694427b3 100644 +index 8746997c9b849ac5c952fdbe0a8dd608d6680a3a..6bd07804e2fd0b023f48729a3fa1d386071b74bd 100644 --- a/es/rrweb/packages/rrweb/src/record/observers/canvas/canvas-manager.js +++ b/es/rrweb/packages/rrweb/src/record/observers/canvas/canvas-manager.js -@@ -118,6 +118,12 @@ class CanvasManager { - context.clear(context.COLOR_BUFFER_BIT); - } - } +@@ -1,184 +1,243 @@ +-import { __rest, __awaiter } from './../../../../../../ext/tslib/tslib.es6.js'; +-import { isBlocked } from '../../../utils.js'; +-import { CanvasContext } from '../../../../../types/dist/types.js'; +-import initCanvas2DMutationObserver from './2d.js'; +-import initCanvasContextObserver from './canvas.js'; +-import initCanvasWebGLMutationObserver from './webgl.js'; +-import WorkerFactory from '../../../../../../_virtual/image-bitmap-data-url-worker.js'; ++import { __rest, __awaiter } from "./../../../../../../ext/tslib/tslib.es6.js"; ++import { isBlocked } from "../../../utils.js"; ++import { CanvasContext } from "../../../../../types/dist/types.js"; ++import initCanvas2DMutationObserver from "./2d.js"; ++import initCanvasContextObserver from "./canvas.js"; ++import initCanvasWebGLMutationObserver from "./webgl.js"; ++import WorkerFactory from "../../../../../../_virtual/image-bitmap-data-url-worker.js"; + + class CanvasManager { +- reset() { +- this.pendingCanvasMutations.clear(); +- this.resetObservers && this.resetObservers(); +- } +- freeze() { +- this.frozen = true; +- } +- unfreeze() { +- this.frozen = false; +- } +- lock() { +- this.locked = true; +- } +- unlock() { +- this.locked = false; +- } +- constructor(options) { +- this.pendingCanvasMutations = new Map(); +- this.rafStamps = { latestId: 0, invokeId: null }; +- this.frozen = false; +- this.locked = false; +- this.processMutation = (target, mutation) => { +- const newFrame = this.rafStamps.invokeId && +- this.rafStamps.latestId !== this.rafStamps.invokeId; +- if (newFrame || !this.rafStamps.invokeId) +- this.rafStamps.invokeId = this.rafStamps.latestId; +- if (!this.pendingCanvasMutations.has(target)) { +- this.pendingCanvasMutations.set(target, []); +- } +- this.pendingCanvasMutations.get(target).push(mutation); +- }; +- const { sampling = 'all', win, blockClass, blockSelector, recordCanvas, dataURLOptions, } = options; +- this.mutationCb = options.mutationCb; +- this.mirror = options.mirror; +- if (recordCanvas && sampling === 'all') +- this.initCanvasMutationObserver(win, blockClass, blockSelector); +- if (recordCanvas && typeof sampling === 'number') +- this.initCanvasFPSObserver(sampling, win, blockClass, blockSelector, { +- dataURLOptions, +- }); +- } +- initCanvasFPSObserver(fps, win, blockClass, blockSelector, options) { +- const canvasContextReset = initCanvasContextObserver(win, blockClass, blockSelector, true); +- const snapshotInProgressMap = new Map(); +- const worker = new WorkerFactory(); +- worker.onmessage = (e) => { +- const { id } = e.data; +- snapshotInProgressMap.set(id, false); +- if (!('base64' in e.data)) +- return; +- const { base64, type, width, height } = e.data; +- this.mutationCb({ +- id, +- type: CanvasContext['2D'], +- commands: [ +- { +- property: 'clearRect', +- args: [0, 0, width, height], +- }, +- { +- property: 'drawImage', +- args: [ +- { +- rr_type: 'ImageBitmap', +- args: [ +- { +- rr_type: 'Blob', +- data: [{ rr_type: 'ArrayBuffer', base64 }], +- type, +- }, +- ], +- }, +- 0, +- 0, +- ], +- }, ++ reset() { ++ this.pendingCanvasMutations.clear(); ++ this.resetObservers && this.resetObservers(); ++ } ++ freeze() { ++ this.frozen = true; ++ } ++ unfreeze() { ++ this.frozen = false; ++ } ++ lock() { ++ this.locked = true; ++ } ++ unlock() { ++ this.locked = false; ++ } ++ constructor(options) { ++ this.pendingCanvasMutations = new Map(); ++ this.rafStamps = { latestId: 0, invokeId: null }; ++ this.frozen = false; ++ this.locked = false; ++ this.processMutation = (target, mutation) => { ++ const newFrame = ++ this.rafStamps.invokeId && ++ this.rafStamps.latestId !== this.rafStamps.invokeId; ++ if (newFrame || !this.rafStamps.invokeId) ++ this.rafStamps.invokeId = this.rafStamps.latestId; ++ if (!this.pendingCanvasMutations.has(target)) { ++ this.pendingCanvasMutations.set(target, []); ++ } ++ this.pendingCanvasMutations.get(target).push(mutation); ++ }; ++ const { ++ sampling = "all", ++ win, ++ blockClass, ++ blockSelector, ++ recordCanvas, ++ dataURLOptions, ++ } = options; ++ this.mutationCb = options.mutationCb; ++ this.mirror = options.mirror; ++ if (recordCanvas && sampling === "all") ++ this.initCanvasMutationObserver(win, blockClass, blockSelector); ++ if (recordCanvas && typeof sampling === "number") ++ this.initCanvasFPSObserver(sampling, win, blockClass, blockSelector, { ++ dataURLOptions, ++ }); ++ } ++ initCanvasFPSObserver(fps, win, blockClass, blockSelector, options) { ++ const canvasContextReset = initCanvasContextObserver( ++ win, ++ blockClass, ++ blockSelector, ++ true ++ ); ++ const snapshotInProgressMap = new Map(); ++ const worker = new WorkerFactory(); ++ worker.onmessage = (e) => { ++ const { id } = e.data; ++ snapshotInProgressMap.set(id, false); ++ if (!("base64" in e.data)) return; ++ const { base64, type, width, height } = e.data; ++ this.mutationCb({ ++ id, ++ type: CanvasContext["2D"], ++ commands: [ ++ { ++ property: "clearRect", ++ args: [0, 0, width, height], ++ }, ++ { ++ property: "drawImage", ++ args: [ ++ { ++ rr_type: "ImageBitmap", ++ args: [ ++ { ++ rr_type: "Blob", ++ data: [{ rr_type: "ArrayBuffer", base64 }], ++ type, ++ }, + ], +- }); +- }; +- const timeBetweenSnapshots = 1000 / fps; +- let lastSnapshotTime = 0; +- let rafId; +- const getCanvas = () => { +- const matchedCanvas = []; +- win.document.querySelectorAll('canvas').forEach((canvas) => { +- if (!isBlocked(canvas, blockClass, blockSelector, true)) { +- matchedCanvas.push(canvas); +- } +- }); +- return matchedCanvas; +- }; +- const takeCanvasSnapshots = (timestamp) => { +- if (lastSnapshotTime && +- timestamp - lastSnapshotTime < timeBetweenSnapshots) { +- rafId = requestAnimationFrame(takeCanvasSnapshots); +- return; +- } +- lastSnapshotTime = timestamp; +- getCanvas() +- .forEach((canvas) => __awaiter(this, void 0, void 0, function* () { +- var _a; +- const id = this.mirror.getId(canvas); +- if (snapshotInProgressMap.get(id)) +- return; +- snapshotInProgressMap.set(id, true); +- if (['webgl', 'webgl2'].includes(canvas.__context)) { +- const context = canvas.getContext(canvas.__context); +- if (((_a = context === null || context === void 0 ? void 0 : context.getContextAttributes()) === null || _a === void 0 ? void 0 : _a.preserveDrawingBuffer) === false) { +- context.clear(context.COLOR_BUFFER_BIT); +- } +- } +- const bitmap = yield createImageBitmap(canvas); +- worker.postMessage({ +- id, +- bitmap, +- width: canvas.width, +- height: canvas.height, +- dataURLOptions: options.dataURLOptions, +- }, [bitmap]); +- })); +- rafId = requestAnimationFrame(takeCanvasSnapshots); +- }; +- rafId = requestAnimationFrame(takeCanvasSnapshots); +- this.resetObservers = () => { +- canvasContextReset(); +- cancelAnimationFrame(rafId); +- }; +- } +- initCanvasMutationObserver(win, blockClass, blockSelector) { +- this.startRAFTimestamping(); +- this.startPendingCanvasMutationFlusher(); +- const canvasContextReset = initCanvasContextObserver(win, blockClass, blockSelector, false); +- const canvas2DReset = initCanvas2DMutationObserver(this.processMutation.bind(this), win, blockClass, blockSelector); +- const canvasWebGL1and2Reset = initCanvasWebGLMutationObserver(this.processMutation.bind(this), win, blockClass, blockSelector, this.mirror); +- this.resetObservers = () => { +- canvasContextReset(); +- canvas2DReset(); +- canvasWebGL1and2Reset(); +- }; +- } +- startPendingCanvasMutationFlusher() { +- requestAnimationFrame(() => this.flushPendingCanvasMutations()); +- } +- startRAFTimestamping() { +- const setLatestRAFTimestamp = (timestamp) => { +- this.rafStamps.latestId = timestamp; +- requestAnimationFrame(setLatestRAFTimestamp); +- }; +- requestAnimationFrame(setLatestRAFTimestamp); +- } +- flushPendingCanvasMutations() { +- this.pendingCanvasMutations.forEach((values, canvas) => { +- const id = this.mirror.getId(canvas); +- this.flushPendingCanvasMutationFor(canvas, id); ++ }, ++ 0, ++ 0, ++ ], ++ }, ++ ], ++ }); ++ }; ++ const timeBetweenSnapshots = 1000 / fps; ++ let lastSnapshotTime = 0; ++ let rafId; ++ const getCanvas = () => { ++ const matchedCanvas = []; ++ const searchCanvas = (root) => { ++ root.querySelectorAll("canvas").forEach((canvas) => { ++ if (!isBlocked(canvas, blockClass, blockSelector, true)) { ++ matchedCanvas.push(canvas); ++ } + }); +- requestAnimationFrame(() => this.flushPendingCanvasMutations()); +- } +- flushPendingCanvasMutationFor(canvas, id) { +- if (this.frozen || this.locked) { +- return; +- } +- const valuesWithType = this.pendingCanvasMutations.get(canvas); +- if (!valuesWithType || id === -1) +- return; +- const values = valuesWithType.map((value) => { +- const rest = __rest(value, ["type"]); +- return rest; ++ root.querySelectorAll("*").forEach((elem) => { ++ if (elem.shadowRoot) { ++ searchCanvas(elem.shadowRoot); ++ } + }); +- const { type } = valuesWithType[0]; +- this.mutationCb({ id, type, commands: values }); +- this.pendingCanvasMutations.delete(canvas); ++ }; ++ searchCanvas(win.document); ++ return matchedCanvas; ++ }; ++ const takeCanvasSnapshots = (timestamp) => { ++ if ( ++ lastSnapshotTime && ++ timestamp - lastSnapshotTime < timeBetweenSnapshots ++ ) { ++ rafId = requestAnimationFrame(takeCanvasSnapshots); ++ return; ++ } ++ lastSnapshotTime = timestamp; ++ getCanvas().forEach((canvas) => ++ __awaiter(this, void 0, void 0, function* () { ++ var _a; ++ const id = this.mirror.getId(canvas); ++ if (snapshotInProgressMap.get(id)) return; ++ snapshotInProgressMap.set(id, true); ++ if (["webgl", "webgl2"].includes(canvas.__context)) { ++ const context = canvas.getContext(canvas.__context); ++ if ( ++ ((_a = ++ context === null || context === void 0 ++ ? void 0 ++ : context.getContextAttributes()) === null || _a === void 0 ++ ? void 0 ++ : _a.preserveDrawingBuffer) === false ++ ) { ++ context.clear(context.COLOR_BUFFER_BIT); ++ } ++ } ++ ++ // The browser throws if the canvas is 0 in size ++ // Uncaught (in promise) DOMException: Failed to execute 'createImageBitmap' on 'Window': The source image width is 0. ++ // Assuming the same happens with height ++ if (canvas.width === 0 || canvas.height === 0) return; + -+ // The browser throws if the canvas is 0 in size -+ // Uncaught (in promise) DOMException: Failed to execute 'createImageBitmap' on 'Window': The source image width is 0. -+ // Assuming the same happens with height -+ if (canvas.width === 0 || canvas.height === 0) return; -+ - const bitmap = yield createImageBitmap(canvas); - worker.postMessage({ - id, ++ const width = canvas.clientWidth; ++ const height = canvas.clientHeight; ++ ++ const bitmap = yield createImageBitmap(canvas, { ++ resizeWidth: width, ++ resizeHeight: height, ++ }); ++ worker.postMessage( ++ { ++ id, ++ bitmap, ++ width: width, ++ height: height, ++ dataURLOptions: options.dataURLOptions, ++ }, ++ [bitmap] ++ ); ++ }) ++ ); ++ rafId = requestAnimationFrame(takeCanvasSnapshots); ++ }; ++ rafId = requestAnimationFrame(takeCanvasSnapshots); ++ this.resetObservers = () => { ++ canvasContextReset(); ++ cancelAnimationFrame(rafId); ++ }; ++ } ++ initCanvasMutationObserver(win, blockClass, blockSelector) { ++ this.startRAFTimestamping(); ++ this.startPendingCanvasMutationFlusher(); ++ const canvasContextReset = initCanvasContextObserver( ++ win, ++ blockClass, ++ blockSelector, ++ false ++ ); ++ const canvas2DReset = initCanvas2DMutationObserver( ++ this.processMutation.bind(this), ++ win, ++ blockClass, ++ blockSelector ++ ); ++ const canvasWebGL1and2Reset = initCanvasWebGLMutationObserver( ++ this.processMutation.bind(this), ++ win, ++ blockClass, ++ blockSelector, ++ this.mirror ++ ); ++ this.resetObservers = () => { ++ canvasContextReset(); ++ canvas2DReset(); ++ canvasWebGL1and2Reset(); ++ }; ++ } ++ startPendingCanvasMutationFlusher() { ++ requestAnimationFrame(() => this.flushPendingCanvasMutations()); ++ } ++ startRAFTimestamping() { ++ const setLatestRAFTimestamp = (timestamp) => { ++ this.rafStamps.latestId = timestamp; ++ requestAnimationFrame(setLatestRAFTimestamp); ++ }; ++ requestAnimationFrame(setLatestRAFTimestamp); ++ } ++ flushPendingCanvasMutations() { ++ this.pendingCanvasMutations.forEach((values, canvas) => { ++ const id = this.mirror.getId(canvas); ++ this.flushPendingCanvasMutationFor(canvas, id); ++ }); ++ requestAnimationFrame(() => this.flushPendingCanvasMutations()); ++ } ++ flushPendingCanvasMutationFor(canvas, id) { ++ if (this.frozen || this.locked) { ++ return; + } ++ const valuesWithType = this.pendingCanvasMutations.get(canvas); ++ if (!valuesWithType || id === -1) return; ++ const values = valuesWithType.map((value) => { ++ const rest = __rest(value, ["type"]); ++ return rest; ++ }); ++ const { type } = valuesWithType[0]; ++ this.mutationCb({ id, type, commands: values }); ++ this.pendingCanvasMutations.delete(canvas); ++ } + } + + export { CanvasManager }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 752169882..cecaa9255 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: patchedDependencies: rrweb@2.0.0-alpha.11: - hash: tjoktulcrliiekxagfypcvjnr4 + hash: thfhih5chpg3brt4zdipkbrprq path: patches/rrweb@2.0.0-alpha.11.patch dependencies: @@ -170,7 +170,7 @@ devDependencies: version: 5.12.0(rollup@4.9.6) rrweb: specifier: 2.0.0-alpha.11 - version: 2.0.0-alpha.11(patch_hash=tjoktulcrliiekxagfypcvjnr4) + version: 2.0.0-alpha.11(patch_hash=thfhih5chpg3brt4zdipkbrprq) rrweb-snapshot: specifier: 2.0.0-alpha.11 version: 2.0.0-alpha.11 @@ -9216,7 +9216,7 @@ packages: rrweb-snapshot: 1.1.14 dev: true - /rrweb@2.0.0-alpha.11(patch_hash=tjoktulcrliiekxagfypcvjnr4): + /rrweb@2.0.0-alpha.11(patch_hash=thfhih5chpg3brt4zdipkbrprq): resolution: {integrity: sha512-vJ2gNvF+pUG9C2aaau7iSNqhWBSc4BwtUO4FpegOtDObuH4PIaxNJOlgHz82+WxKr9XPm93ER0LqmNpy0KYdKg==} dependencies: '@rrweb/types': 2.0.0-alpha.11 From 2792e663fd0c70ce88df6c28209a79ab6e00cb0e Mon Sep 17 00:00:00 2001 From: David Newell Date: Fri, 15 Mar 2024 14:17:19 +0000 Subject: [PATCH 2/2] disable prettier --- patches/rrweb@2.0.0-alpha.11.patch | 469 ++++------------------------- pnpm-lock.yaml | 6 +- 2 files changed, 60 insertions(+), 415 deletions(-) diff --git a/patches/rrweb@2.0.0-alpha.11.patch b/patches/rrweb@2.0.0-alpha.11.patch index ee59a78d3..2bb5ab4fd 100644 --- a/patches/rrweb@2.0.0-alpha.11.patch +++ b/patches/rrweb@2.0.0-alpha.11.patch @@ -1133,425 +1133,70 @@ index 81ad4520d1435d088752a85f9a55a68ad6e0f514..e19dec6cc98ea782d53328853ca1b46d export { MutationBuffer as default }; diff --git a/es/rrweb/packages/rrweb/src/record/observers/canvas/canvas-manager.js b/es/rrweb/packages/rrweb/src/record/observers/canvas/canvas-manager.js -index 8746997c9b849ac5c952fdbe0a8dd608d6680a3a..6bd07804e2fd0b023f48729a3fa1d386071b74bd 100644 +index 8746997c9b849ac5c952fdbe0a8dd608d6680a3a..1d9bdac62dff11370df08ee7b4a6ce75ce594eb5 100644 --- a/es/rrweb/packages/rrweb/src/record/observers/canvas/canvas-manager.js +++ b/es/rrweb/packages/rrweb/src/record/observers/canvas/canvas-manager.js -@@ -1,184 +1,243 @@ --import { __rest, __awaiter } from './../../../../../../ext/tslib/tslib.es6.js'; --import { isBlocked } from '../../../utils.js'; --import { CanvasContext } from '../../../../../types/dist/types.js'; --import initCanvas2DMutationObserver from './2d.js'; --import initCanvasContextObserver from './canvas.js'; --import initCanvasWebGLMutationObserver from './webgl.js'; --import WorkerFactory from '../../../../../../_virtual/image-bitmap-data-url-worker.js'; -+import { __rest, __awaiter } from "./../../../../../../ext/tslib/tslib.es6.js"; -+import { isBlocked } from "../../../utils.js"; -+import { CanvasContext } from "../../../../../types/dist/types.js"; -+import initCanvas2DMutationObserver from "./2d.js"; -+import initCanvasContextObserver from "./canvas.js"; -+import initCanvasWebGLMutationObserver from "./webgl.js"; -+import WorkerFactory from "../../../../../../_virtual/image-bitmap-data-url-worker.js"; - - class CanvasManager { -- reset() { -- this.pendingCanvasMutations.clear(); -- this.resetObservers && this.resetObservers(); -- } -- freeze() { -- this.frozen = true; -- } -- unfreeze() { -- this.frozen = false; -- } -- lock() { -- this.locked = true; -- } -- unlock() { -- this.locked = false; -- } -- constructor(options) { -- this.pendingCanvasMutations = new Map(); -- this.rafStamps = { latestId: 0, invokeId: null }; -- this.frozen = false; -- this.locked = false; -- this.processMutation = (target, mutation) => { -- const newFrame = this.rafStamps.invokeId && -- this.rafStamps.latestId !== this.rafStamps.invokeId; -- if (newFrame || !this.rafStamps.invokeId) -- this.rafStamps.invokeId = this.rafStamps.latestId; -- if (!this.pendingCanvasMutations.has(target)) { -- this.pendingCanvasMutations.set(target, []); -- } -- this.pendingCanvasMutations.get(target).push(mutation); -- }; -- const { sampling = 'all', win, blockClass, blockSelector, recordCanvas, dataURLOptions, } = options; -- this.mutationCb = options.mutationCb; -- this.mirror = options.mirror; -- if (recordCanvas && sampling === 'all') -- this.initCanvasMutationObserver(win, blockClass, blockSelector); -- if (recordCanvas && typeof sampling === 'number') -- this.initCanvasFPSObserver(sampling, win, blockClass, blockSelector, { -- dataURLOptions, -- }); -- } -- initCanvasFPSObserver(fps, win, blockClass, blockSelector, options) { -- const canvasContextReset = initCanvasContextObserver(win, blockClass, blockSelector, true); -- const snapshotInProgressMap = new Map(); -- const worker = new WorkerFactory(); -- worker.onmessage = (e) => { -- const { id } = e.data; -- snapshotInProgressMap.set(id, false); -- if (!('base64' in e.data)) -- return; -- const { base64, type, width, height } = e.data; -- this.mutationCb({ -- id, -- type: CanvasContext['2D'], -- commands: [ -- { -- property: 'clearRect', -- args: [0, 0, width, height], -- }, -- { -- property: 'drawImage', -- args: [ -- { -- rr_type: 'ImageBitmap', -- args: [ -- { -- rr_type: 'Blob', -- data: [{ rr_type: 'ArrayBuffer', base64 }], -- type, -- }, -- ], -- }, -- 0, -- 0, -- ], -- }, -+ reset() { -+ this.pendingCanvasMutations.clear(); -+ this.resetObservers && this.resetObservers(); -+ } -+ freeze() { -+ this.frozen = true; -+ } -+ unfreeze() { -+ this.frozen = false; -+ } -+ lock() { -+ this.locked = true; -+ } -+ unlock() { -+ this.locked = false; -+ } -+ constructor(options) { -+ this.pendingCanvasMutations = new Map(); -+ this.rafStamps = { latestId: 0, invokeId: null }; -+ this.frozen = false; -+ this.locked = false; -+ this.processMutation = (target, mutation) => { -+ const newFrame = -+ this.rafStamps.invokeId && -+ this.rafStamps.latestId !== this.rafStamps.invokeId; -+ if (newFrame || !this.rafStamps.invokeId) -+ this.rafStamps.invokeId = this.rafStamps.latestId; -+ if (!this.pendingCanvasMutations.has(target)) { -+ this.pendingCanvasMutations.set(target, []); -+ } -+ this.pendingCanvasMutations.get(target).push(mutation); -+ }; -+ const { -+ sampling = "all", -+ win, -+ blockClass, -+ blockSelector, -+ recordCanvas, -+ dataURLOptions, -+ } = options; -+ this.mutationCb = options.mutationCb; -+ this.mirror = options.mirror; -+ if (recordCanvas && sampling === "all") -+ this.initCanvasMutationObserver(win, blockClass, blockSelector); -+ if (recordCanvas && typeof sampling === "number") -+ this.initCanvasFPSObserver(sampling, win, blockClass, blockSelector, { -+ dataURLOptions, -+ }); -+ } -+ initCanvasFPSObserver(fps, win, blockClass, blockSelector, options) { -+ const canvasContextReset = initCanvasContextObserver( -+ win, -+ blockClass, -+ blockSelector, -+ true -+ ); -+ const snapshotInProgressMap = new Map(); -+ const worker = new WorkerFactory(); -+ worker.onmessage = (e) => { -+ const { id } = e.data; -+ snapshotInProgressMap.set(id, false); -+ if (!("base64" in e.data)) return; -+ const { base64, type, width, height } = e.data; -+ this.mutationCb({ -+ id, -+ type: CanvasContext["2D"], -+ commands: [ -+ { -+ property: "clearRect", -+ args: [0, 0, width, height], -+ }, -+ { -+ property: "drawImage", -+ args: [ -+ { -+ rr_type: "ImageBitmap", -+ args: [ -+ { -+ rr_type: "Blob", -+ data: [{ rr_type: "ArrayBuffer", base64 }], -+ type, -+ }, - ], -- }); -- }; -- const timeBetweenSnapshots = 1000 / fps; -- let lastSnapshotTime = 0; -- let rafId; -- const getCanvas = () => { -- const matchedCanvas = []; +@@ -91,11 +91,22 @@ class CanvasManager { + let rafId; + const getCanvas = () => { + const matchedCanvas = []; - win.document.querySelectorAll('canvas').forEach((canvas) => { - if (!isBlocked(canvas, blockClass, blockSelector, true)) { - matchedCanvas.push(canvas); - } - }); -- return matchedCanvas; -- }; -- const takeCanvasSnapshots = (timestamp) => { -- if (lastSnapshotTime && -- timestamp - lastSnapshotTime < timeBetweenSnapshots) { -- rafId = requestAnimationFrame(takeCanvasSnapshots); -- return; -- } -- lastSnapshotTime = timestamp; -- getCanvas() -- .forEach((canvas) => __awaiter(this, void 0, void 0, function* () { -- var _a; -- const id = this.mirror.getId(canvas); -- if (snapshotInProgressMap.get(id)) -- return; -- snapshotInProgressMap.set(id, true); -- if (['webgl', 'webgl2'].includes(canvas.__context)) { -- const context = canvas.getContext(canvas.__context); -- if (((_a = context === null || context === void 0 ? void 0 : context.getContextAttributes()) === null || _a === void 0 ? void 0 : _a.preserveDrawingBuffer) === false) { -- context.clear(context.COLOR_BUFFER_BIT); -- } -- } -- const bitmap = yield createImageBitmap(canvas); -- worker.postMessage({ -- id, -- bitmap, -- width: canvas.width, -- height: canvas.height, -- dataURLOptions: options.dataURLOptions, -- }, [bitmap]); -- })); -- rafId = requestAnimationFrame(takeCanvasSnapshots); -- }; -- rafId = requestAnimationFrame(takeCanvasSnapshots); -- this.resetObservers = () => { -- canvasContextReset(); -- cancelAnimationFrame(rafId); -- }; -- } -- initCanvasMutationObserver(win, blockClass, blockSelector) { -- this.startRAFTimestamping(); -- this.startPendingCanvasMutationFlusher(); -- const canvasContextReset = initCanvasContextObserver(win, blockClass, blockSelector, false); -- const canvas2DReset = initCanvas2DMutationObserver(this.processMutation.bind(this), win, blockClass, blockSelector); -- const canvasWebGL1and2Reset = initCanvasWebGLMutationObserver(this.processMutation.bind(this), win, blockClass, blockSelector, this.mirror); -- this.resetObservers = () => { -- canvasContextReset(); -- canvas2DReset(); -- canvasWebGL1and2Reset(); -- }; -- } -- startPendingCanvasMutationFlusher() { -- requestAnimationFrame(() => this.flushPendingCanvasMutations()); -- } -- startRAFTimestamping() { -- const setLatestRAFTimestamp = (timestamp) => { -- this.rafStamps.latestId = timestamp; -- requestAnimationFrame(setLatestRAFTimestamp); -- }; -- requestAnimationFrame(setLatestRAFTimestamp); -- } -- flushPendingCanvasMutations() { -- this.pendingCanvasMutations.forEach((values, canvas) => { -- const id = this.mirror.getId(canvas); -- this.flushPendingCanvasMutationFor(canvas, id); -+ }, -+ 0, -+ 0, -+ ], -+ }, -+ ], -+ }); -+ }; -+ const timeBetweenSnapshots = 1000 / fps; -+ let lastSnapshotTime = 0; -+ let rafId; -+ const getCanvas = () => { -+ const matchedCanvas = []; -+ const searchCanvas = (root) => { -+ root.querySelectorAll("canvas").forEach((canvas) => { -+ if (!isBlocked(canvas, blockClass, blockSelector, true)) { -+ matchedCanvas.push(canvas); -+ } - }); -- requestAnimationFrame(() => this.flushPendingCanvasMutations()); -- } -- flushPendingCanvasMutationFor(canvas, id) { -- if (this.frozen || this.locked) { -- return; -- } -- const valuesWithType = this.pendingCanvasMutations.get(canvas); -- if (!valuesWithType || id === -1) -- return; -- const values = valuesWithType.map((value) => { -- const rest = __rest(value, ["type"]); -- return rest; -+ root.querySelectorAll("*").forEach((elem) => { -+ if (elem.shadowRoot) { -+ searchCanvas(elem.shadowRoot); -+ } - }); -- const { type } = valuesWithType[0]; -- this.mutationCb({ id, type, commands: values }); -- this.pendingCanvasMutations.delete(canvas); -+ }; -+ searchCanvas(win.document); -+ return matchedCanvas; -+ }; -+ const takeCanvasSnapshots = (timestamp) => { -+ if ( -+ lastSnapshotTime && -+ timestamp - lastSnapshotTime < timeBetweenSnapshots -+ ) { -+ rafId = requestAnimationFrame(takeCanvasSnapshots); -+ return; -+ } -+ lastSnapshotTime = timestamp; -+ getCanvas().forEach((canvas) => -+ __awaiter(this, void 0, void 0, function* () { -+ var _a; -+ const id = this.mirror.getId(canvas); -+ if (snapshotInProgressMap.get(id)) return; -+ snapshotInProgressMap.set(id, true); -+ if (["webgl", "webgl2"].includes(canvas.__context)) { -+ const context = canvas.getContext(canvas.__context); -+ if ( -+ ((_a = -+ context === null || context === void 0 -+ ? void 0 -+ : context.getContextAttributes()) === null || _a === void 0 -+ ? void 0 -+ : _a.preserveDrawingBuffer) === false -+ ) { -+ context.clear(context.COLOR_BUFFER_BIT); -+ } -+ } + -+ // The browser throws if the canvas is 0 in size -+ // Uncaught (in promise) DOMException: Failed to execute 'createImageBitmap' on 'Window': The source image width is 0. -+ // Assuming the same happens with height -+ if (canvas.width === 0 || canvas.height === 0) return; ++ const searchCanvas = (root) => { ++ root.querySelectorAll('canvas').forEach((canvas) => { ++ if (!isBlocked(canvas, blockClass, blockSelector, true)) { ++ matchedCanvas.push(canvas); ++ } ++ }); ++ root.querySelectorAll('*').forEach((elem) => { ++ if (elem.shadowRoot) { ++ searchCanvas(elem.shadowRoot); ++ } ++ }); ++ }; + -+ const width = canvas.clientWidth; -+ const height = canvas.clientHeight; ++ searchCanvas(win.document); + -+ const bitmap = yield createImageBitmap(canvas, { -+ resizeWidth: width, -+ resizeHeight: height, -+ }); -+ worker.postMessage( -+ { -+ id, -+ bitmap, -+ width: width, -+ height: height, -+ dataURLOptions: options.dataURLOptions, -+ }, -+ [bitmap] -+ ); -+ }) -+ ); -+ rafId = requestAnimationFrame(takeCanvasSnapshots); -+ }; -+ rafId = requestAnimationFrame(takeCanvasSnapshots); -+ this.resetObservers = () => { -+ canvasContextReset(); -+ cancelAnimationFrame(rafId); -+ }; -+ } -+ initCanvasMutationObserver(win, blockClass, blockSelector) { -+ this.startRAFTimestamping(); -+ this.startPendingCanvasMutationFlusher(); -+ const canvasContextReset = initCanvasContextObserver( -+ win, -+ blockClass, -+ blockSelector, -+ false -+ ); -+ const canvas2DReset = initCanvas2DMutationObserver( -+ this.processMutation.bind(this), -+ win, -+ blockClass, -+ blockSelector -+ ); -+ const canvasWebGL1and2Reset = initCanvasWebGLMutationObserver( -+ this.processMutation.bind(this), -+ win, -+ blockClass, -+ blockSelector, -+ this.mirror -+ ); -+ this.resetObservers = () => { -+ canvasContextReset(); -+ canvas2DReset(); -+ canvasWebGL1and2Reset(); -+ }; -+ } -+ startPendingCanvasMutationFlusher() { -+ requestAnimationFrame(() => this.flushPendingCanvasMutations()); -+ } -+ startRAFTimestamping() { -+ const setLatestRAFTimestamp = (timestamp) => { -+ this.rafStamps.latestId = timestamp; -+ requestAnimationFrame(setLatestRAFTimestamp); -+ }; -+ requestAnimationFrame(setLatestRAFTimestamp); -+ } -+ flushPendingCanvasMutations() { -+ this.pendingCanvasMutations.forEach((values, canvas) => { -+ const id = this.mirror.getId(canvas); -+ this.flushPendingCanvasMutationFor(canvas, id); -+ }); -+ requestAnimationFrame(() => this.flushPendingCanvasMutations()); -+ } -+ flushPendingCanvasMutationFor(canvas, id) { -+ if (this.frozen || this.locked) { -+ return; - } -+ const valuesWithType = this.pendingCanvasMutations.get(canvas); -+ if (!valuesWithType || id === -1) return; -+ const values = valuesWithType.map((value) => { -+ const rest = __rest(value, ["type"]); -+ return rest; -+ }); -+ const { type } = valuesWithType[0]; -+ this.mutationCb({ id, type, commands: values }); -+ this.pendingCanvasMutations.delete(canvas); -+ } - } - - export { CanvasManager }; + return matchedCanvas; + }; + const takeCanvasSnapshots = (timestamp) => { +@@ -111,6 +122,12 @@ class CanvasManager { + const id = this.mirror.getId(canvas); + if (snapshotInProgressMap.get(id)) + return; ++ ++ // The browser throws if the canvas is 0 in size ++ // Uncaught (in promise) DOMException: Failed to execute 'createImageBitmap' on 'Window': The source image width is 0. ++ // Assuming the same happens with height ++ if (canvas.width === 0 || canvas.height === 0) return; ++ + snapshotInProgressMap.set(id, true); + if (['webgl', 'webgl2'].includes(canvas.__context)) { + const context = canvas.getContext(canvas.__context); +@@ -118,12 +135,19 @@ class CanvasManager { + context.clear(context.COLOR_BUFFER_BIT); + } + } +- const bitmap = yield createImageBitmap(canvas); ++ ++ const width = canvas.clientWidth; ++ const height = canvas.clientHeight; ++ ++ const bitmap = yield createImageBitmap(canvas, { ++ resizeWidth: width, ++ resizeHeight: height ++ }) + worker.postMessage({ + id, + bitmap, +- width: canvas.width, +- height: canvas.height, ++ width: width, ++ height: height, + dataURLOptions: options.dataURLOptions, + }, [bitmap]); + })); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 50a8d5855..ac11bc352 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: patchedDependencies: rrweb@2.0.0-alpha.11: - hash: thfhih5chpg3brt4zdipkbrprq + hash: ldhsspyvwscjgedotm5w5frkg4 path: patches/rrweb@2.0.0-alpha.11.patch dependencies: @@ -170,7 +170,7 @@ devDependencies: version: 5.12.0(rollup@4.9.6) rrweb: specifier: 2.0.0-alpha.11 - version: 2.0.0-alpha.11(patch_hash=thfhih5chpg3brt4zdipkbrprq) + version: 2.0.0-alpha.11(patch_hash=ldhsspyvwscjgedotm5w5frkg4) rrweb-snapshot: specifier: 2.0.0-alpha.11 version: 2.0.0-alpha.11 @@ -9194,7 +9194,7 @@ packages: resolution: {integrity: sha512-N0dzeJA2VhrlSOadkKwCVmV/DuNOwBH+Lhx89hAf9PQK4lCS8AP4AaylhqUdZOYHqwVjqsYel/uZ4hN79vuLhw==} dev: true - /rrweb@2.0.0-alpha.11(patch_hash=thfhih5chpg3brt4zdipkbrprq): + /rrweb@2.0.0-alpha.11(patch_hash=ldhsspyvwscjgedotm5w5frkg4): resolution: {integrity: sha512-vJ2gNvF+pUG9C2aaau7iSNqhWBSc4BwtUO4FpegOtDObuH4PIaxNJOlgHz82+WxKr9XPm93ER0LqmNpy0KYdKg==} dependencies: '@rrweb/types': 2.0.0-alpha.11