diff --git a/.changeset/fresh-spoons-drive.md b/.changeset/fresh-spoons-drive.md new file mode 100644 index 0000000000..b99410373c --- /dev/null +++ b/.changeset/fresh-spoons-drive.md @@ -0,0 +1,5 @@ +--- +'rrweb': patch +--- + +Fix: processed-node-manager is created even in the environment that doesn't need a recorder diff --git a/guide.zh_CN.md b/guide.zh_CN.md index 8e6f2c5a2b..28c846c0ac 100644 --- a/guide.zh_CN.md +++ b/guide.zh_CN.md @@ -159,6 +159,7 @@ setInterval(save, 10 * 1000); | collectFonts | false | 是否记录页面中的字体文件 | | userTriggeredOnInput | false | [什么是 `userTriggered`](https://github.com/rrweb-io/rrweb/pull/495) | | plugins | [] | 加载插件以获得额外的录制功能. [什么是插件?](./docs/recipes/plugin.zh_CN.md) | +| errorHandler | - | 一个可以定制化处理错误的毁掉函数,它的参数是错误对象。如果 rrweb recorder 内部的某些内容抛出错误,则会调用该回调。 | #### 隐私 diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts index 54d8eafa2a..c0e69a025f 100644 --- a/packages/rrweb/src/record/index.ts +++ b/packages/rrweb/src/record/index.ts @@ -4,11 +4,7 @@ import { SlimDOMOptions, createMirror, } from 'rrweb-snapshot'; -import { - initObservers, - mutationBuffers, - processedNodeManager, -} from './observer'; +import { initObservers, mutationBuffers } from './observer'; import { on, getWindowWidth, @@ -36,6 +32,7 @@ import { IframeManager } from './iframe-manager'; import { ShadowDomManager } from './shadow-dom-manager'; import { CanvasManager } from './observers/canvas/canvas-manager'; import { StylesheetManager } from './stylesheet-manager'; +import ProcessedNodeManager from './processed-node-manager'; import { callbackWrapper, registerErrorHandler, @@ -306,6 +303,8 @@ function record( }); } + const processedNodeManager = new ProcessedNodeManager(); + canvasManager = new CanvasManager({ recordCanvas, mutationCb: wrappedCanvasMutationEmit, @@ -616,6 +615,7 @@ function record( } return () => { handlers.forEach((h) => h()); + processedNodeManager.destroy(); recording = false; unregisterErrorHandler(); }; diff --git a/packages/rrweb/src/record/observer.ts b/packages/rrweb/src/record/observer.ts index d02c963983..d2852b970a 100644 --- a/packages/rrweb/src/record/observer.ts +++ b/packages/rrweb/src/record/observer.ts @@ -41,7 +41,6 @@ import { selectionCallback, } from '@rrweb/types'; import MutationBuffer from './mutation'; -import ProcessedNodeManager from './processed-node-manager'; import { callbackWrapper } from './error-handler'; type WindowWithStoredMutationObserver = IWindow & { @@ -54,7 +53,6 @@ type WindowWithAngularZone = IWindow & { }; export const mutationBuffers: MutationBuffer[] = []; -export const processedNodeManager = new ProcessedNodeManager(); // Event.path is non-standard and used in some older browsers type NonStandardEvent = Omit & { diff --git a/packages/rrweb/src/record/processed-node-manager.ts b/packages/rrweb/src/record/processed-node-manager.ts index 60b939bb12..b5d6c4b679 100644 --- a/packages/rrweb/src/record/processed-node-manager.ts +++ b/packages/rrweb/src/record/processed-node-manager.ts @@ -5,6 +5,8 @@ import type MutationBuffer from './mutation'; */ export default class ProcessedNodeManager { private nodeMap: WeakMap> = new WeakMap(); + // Whether to continue RAF loop. + private loop = true; constructor() { this.periodicallyClear(); @@ -13,7 +15,7 @@ export default class ProcessedNodeManager { private periodicallyClear() { requestAnimationFrame(() => { this.clear(); - this.periodicallyClear(); + if (this.loop) this.periodicallyClear(); }); } @@ -31,4 +33,9 @@ export default class ProcessedNodeManager { private clear() { this.nodeMap = new WeakMap(); } + + public destroy() { + // Stop the RAF loop. + this.loop = false; + } } diff --git a/packages/rrweb/src/utils.ts b/packages/rrweb/src/utils.ts index 2ed5ba52d3..1626e3734c 100644 --- a/packages/rrweb/src/utils.ts +++ b/packages/rrweb/src/utils.ts @@ -574,6 +574,7 @@ export function getInputType(element: HTMLElement): Lowercase | null { return element.hasAttribute('data-rr-is-password') ? 'password' : element.hasAttribute('type') - ? (element.getAttribute('type')!.toLowerCase() as Lowercase) + ? // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion + (element.getAttribute('type')!.toLowerCase() as Lowercase) : null; }