From 49349b12e18ed59fc43536fa193872ad2947b1c9 Mon Sep 17 00:00:00 2001 From: Yun Feng Date: Wed, 1 Apr 2026 12:00:00 +0800 Subject: [PATCH] fix duplicate shadow doms in the recorder (#1002) * fix: some shadow doms are observed multiple times and cause duplicate elements in the replayer * fix: in the live mode, the page https://bugs.chromium.org/p/chromium/issues/detail?id=1352333 has duplicate shadow doms in the replayer --- packages/rrweb/scripts/stream.js | 3 +++ packages/rrweb/src/record/shadow-dom-manager.ts | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/packages/rrweb/scripts/stream.js b/packages/rrweb/scripts/stream.js index 6cd099da..394c23e9 100644 --- a/packages/rrweb/scripts/stream.js +++ b/packages/rrweb/scripts/stream.js @@ -227,6 +227,9 @@ void (async () => { 'window.__IS_RECORDING__', ); if (!isRecording) { + // When the page navigates, I notice this event is emitted twice so that there are two recording processes running in a single page. + // Set recording flag True ASAP to prevent recording twice. + await recordedPage.evaluate('window.__IS_RECORDING__ = true'); await startRecording(recordedPage, serverURL); } }); diff --git a/packages/rrweb/src/record/shadow-dom-manager.ts b/packages/rrweb/src/record/shadow-dom-manager.ts index b2aeae01..bfff380e 100644 --- a/packages/rrweb/src/record/shadow-dom-manager.ts +++ b/packages/rrweb/src/record/shadow-dom-manager.ts @@ -17,6 +17,7 @@ type BypassOptions = Omit< }; export class ShadowDomManager { + private shadowDoms = new WeakSet(); private mutationCb: mutationCallBack; private scrollCb: scrollCallback; private bypassOptions: BypassOptions; @@ -55,6 +56,8 @@ export class ShadowDomManager { public addShadowRoot(shadowRoot: ShadowRoot, doc: Document) { if (!isNativeShadowDom(shadowRoot)) return; + if (this.shadowDoms.has(shadowRoot)) return; + this.shadowDoms.add(shadowRoot); initMutationObserver( { ...this.bypassOptions, @@ -106,5 +109,6 @@ export class ShadowDomManager { public reset() { this.restorePatches.forEach((restorePatch) => restorePatch()); + this.shadowDoms = new WeakSet(); } }