From cbdd8b949aa481d91db03be4be05201a06cac74a Mon Sep 17 00:00:00 2001 From: ccschmitz Date: Thu, 15 May 2025 06:23:50 -0500 Subject: [PATCH] Fix infinite loop in replaceActions We were seeing issues getting into an infinite loop calling `splice` to reset actions (I believe only with large action sets). Replacing the array completely seems to get around this issue. --- packages/rrweb/src/replay/index.ts | 7 +++-- packages/rrweb/src/replay/machine.ts | 40 +++++++++++++++------------- packages/rrweb/src/replay/timer.ts | 8 +++++- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/packages/rrweb/src/replay/index.ts b/packages/rrweb/src/replay/index.ts index c84b9a70..248e6d8f 100644 --- a/packages/rrweb/src/replay/index.ts +++ b/packages/rrweb/src/replay/index.ts @@ -54,7 +54,6 @@ import type { incrementalData, Handler, Emitter, - metaEvent, mutationData, scrollData, inputData, @@ -403,7 +402,7 @@ export class Replayer { (e) => e.type === EventType.FullSnapshot, ); if (firstMeta) { - const { width, height } = firstMeta.data as metaEvent['data']; + const { width, height } = firstMeta.data ; setTimeout(() => { this.emitter.emit(ReplayerEvents.Resize, { width, @@ -2019,7 +2018,7 @@ export class Replayer { const svp = styleValues[s] as styleValueWithPriority; targetEl.style.setProperty(s, svp[0], svp[1]); } else { - const svs = styleValues[s] as string; + const svs = styleValues[s] ; targetEl.style.setProperty(s, svs); } } @@ -2252,7 +2251,7 @@ export class Replayer { const adoptStyleSheets = (targetHost: Node, styleIds: number[]) => { const stylesToAdopt = styleIds .map((styleId) => this.styleMirror.getStyle(styleId)) - .filter((style) => style !== null) as CSSStyleSheet[]; + .filter((style) => style !== null); if (hasShadowRoot(targetHost)) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion (targetHost as HTMLElement).shadowRoot!.adoptedStyleSheets = diff --git a/packages/rrweb/src/replay/machine.ts b/packages/rrweb/src/replay/machine.ts index 6f994c52..e8d7ebed 100644 --- a/packages/rrweb/src/replay/machine.ts +++ b/packages/rrweb/src/replay/machine.ts @@ -250,29 +250,31 @@ export function createPlayerService( }), /* Highlight Code Start */ replaceEvents: assign((ctx, machineEvent) => { + if (machineEvent.type !== 'REPLACE_EVENTS') return ctx; + const { events: curEvents, timer, baselineTime } = ctx; - if (machineEvent.type === 'REPLACE_EVENTS') { - const { events: newEvents } = machineEvent.payload; - curEvents.length = 0; - const actions: actionWithDelay[] = []; - for (const event of newEvents) { - addDelay(event, baselineTime); - curEvents.push(event); - if (event.timestamp >= timer.timeOffset + baselineTime) { - const castFn = getCastFn(event, false); - actions.push({ - doAction: () => { - castFn(); - }, - delay: event.delay!, - }); - } - } + const { events: newEvents } = machineEvent.payload; + + if (newEvents.length === 0) return ctx; + + curEvents.length = 0; + const actions: actionWithDelay[] = []; + const timeThreshold = timer.timeOffset + baselineTime; - if (timer.isActive()) { - timer.replaceActions(actions); + for (const event of newEvents) { + addDelay(event, baselineTime); + curEvents.push(event); + if (event.timestamp >= timeThreshold) { + actions.push({ + doAction: getCastFn(event, false), + delay: event.delay!, + }); } } + + if (timer.isActive()) { + timer.replaceActions(actions); + } return { ...ctx, events: curEvents }; }), /* Highlight Code End */ diff --git a/packages/rrweb/src/replay/timer.ts b/packages/rrweb/src/replay/timer.ts index 2233e355..fa90c4a4 100644 --- a/packages/rrweb/src/replay/timer.ts +++ b/packages/rrweb/src/replay/timer.ts @@ -52,8 +52,14 @@ export class Timer { } public replaceActions(actions: actionWithDelay[]) { + const rafWasActive = this.raf === true; + this.actions.length = 0; - this.actions.splice(0, 0, ...actions); + this.actions = [...actions]; + + if (rafWasActive) { + this.raf = requestAnimationFrame(this.rafCheck.bind(this)); + } } /* End Highlight Code */