diff --git a/packages/rrweb/src/replay/index.ts b/packages/rrweb/src/replay/index.ts index e3724d1b14..9c4fe9751a 100644 --- a/packages/rrweb/src/replay/index.ts +++ b/packages/rrweb/src/replay/index.ts @@ -52,6 +52,7 @@ import { getBaseDimension, hasShadowRoot, isSerializedIframe, + uniqueTextMutations, } from '../utils'; import getInjectStyleRules from './styles/inject-style'; import './styles/style.css'; @@ -179,9 +180,10 @@ export class Replayer { this.fragmentParentMap.forEach((parent, frag) => this.restoreRealParent(frag, parent), ); + // apply text needs to happen before virtual style rules gets applied // as it can overwrite the contents of a stylesheet - for (const d of mutationData.texts) { + for (const d of uniqueTextMutations(mutationData.texts)) { this.applyText(d, mutationData); } @@ -1613,7 +1615,7 @@ export class Replayer { Object.assign(this.legacy_missingNodeRetryMap, legacy_missingNodeMap); } - d.texts.forEach((mutation) => { + uniqueTextMutations(d.texts).forEach((mutation) => { let target = this.mirror.getNode(mutation.id); if (!target) { if (d.removes.find((r) => r.id === mutation.id)) { diff --git a/packages/rrweb/src/utils.ts b/packages/rrweb/src/utils.ts index c60174133e..6a556f4aac 100644 --- a/packages/rrweb/src/utils.ts +++ b/packages/rrweb/src/utils.ts @@ -587,3 +587,23 @@ export function hasShadowRoot( ): n is T & { shadowRoot: ShadowRoot } { return Boolean(((n as unknown) as Element)?.shadowRoot); } + +/** + * Returns the latest mutation in the queue for each node. + * @param {textMutation[]} mutations The text mutations to filter. + * @returns {textMutation[]} The filtered text mutations. + */ +export function uniqueTextMutations(mutations: textMutation[]): textMutation[] { + const idSet = new Set(); + const uniqueMutations: textMutation[] = []; + + for (let i = mutations.length; i--; ) { + const mutation = mutations[i]; + if (!idSet.has(mutation.id)) { + uniqueMutations.push(mutation); + idSet.add(mutation.id); + } + } + + return uniqueMutations; +} diff --git a/packages/rrweb/typings/utils.d.ts b/packages/rrweb/typings/utils.d.ts index 5e5a26478b..fb6dcaba94 100644 --- a/packages/rrweb/typings/utils.d.ts +++ b/packages/rrweb/typings/utils.d.ts @@ -10,6 +10,7 @@ export declare function patch(source: { export declare function getWindowHeight(): number; export declare function getWindowWidth(): number; export declare function isBlocked(node: Node | null, blockClass: blockClass): boolean; +export declare function isSerialized(n: Node, mirror: Mirror): boolean; export declare function isIgnored(n: Node, mirror: Mirror): boolean; export declare function isAncestorRemoved(target: Node, mirror: Mirror): boolean; export declare function isTouchEvent(event: MouseEvent | TouchEvent): event is TouchEvent; @@ -62,4 +63,5 @@ export declare function getBaseDimension(node: Node, rootIframe: Node): Document export declare function hasShadowRoot(n: T): n is T & { shadowRoot: ShadowRoot; }; +export declare function getUniqueTextMutations(mutations: textMutation[]): textMutation[]; export {};