From 01abb4dae34d13dcebc688e35a826f4cbf0dd8bf Mon Sep 17 00:00:00 2001 From: Justin Halsall Date: Wed, 4 May 2022 17:43:17 +0200 Subject: [PATCH 1/2] Perf: apply the latest text mutation only --- packages/rrweb/src/replay/index.ts | 6 ++++-- packages/rrweb/src/utils.ts | 16 ++++++++++++++++ packages/rrweb/typings/utils.d.ts | 2 ++ 3 files changed, 22 insertions(+), 2 deletions(-) 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..4a36ea0ba2 100644 --- a/packages/rrweb/src/utils.ts +++ b/packages/rrweb/src/utils.ts @@ -587,3 +587,19 @@ 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 textMuatationsMap = new Map(); + mutations + .slice() + .reverse() + .forEach((text) => { + if (!textMuatationsMap.has(text.id)) textMuatationsMap.set(text.id, text); + }); + return Array.from(textMuatationsMap.values()); +} 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 {}; From f1bf52ea8e50ef3c819827e67340fb8da74cb8d8 Mon Sep 17 00:00:00 2001 From: Justin Halsall Date: Thu, 5 May 2022 11:05:47 +0200 Subject: [PATCH 2/2] Find unique text mutations in one iteration --- packages/rrweb/src/utils.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/rrweb/src/utils.ts b/packages/rrweb/src/utils.ts index 4a36ea0ba2..6a556f4aac 100644 --- a/packages/rrweb/src/utils.ts +++ b/packages/rrweb/src/utils.ts @@ -594,12 +594,16 @@ export function hasShadowRoot( * @returns {textMutation[]} The filtered text mutations. */ export function uniqueTextMutations(mutations: textMutation[]): textMutation[] { - const textMuatationsMap = new Map(); - mutations - .slice() - .reverse() - .forEach((text) => { - if (!textMuatationsMap.has(text.id)) textMuatationsMap.set(text.id, text); - }); - return Array.from(textMuatationsMap.values()); + 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; }