diff --git a/.changeset/swift-pots-search.md b/.changeset/swift-pots-search.md new file mode 100644 index 0000000000..ec13fb6316 --- /dev/null +++ b/.changeset/swift-pots-search.md @@ -0,0 +1,5 @@ +--- +"rrweb": minor +--- + +Optimize isParentRemoved check diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts index 3feae589f6..42170b4940 100644 --- a/packages/rrweb/src/record/mutation.ts +++ b/packages/rrweb/src/record/mutation.ts @@ -169,6 +169,7 @@ export default class MutationBuffer { private addedSet = new Set(); private movedSet = new Set(); private droppedSet = new Set(); + private removesSubTreeCache = new Set(); private mutationCb: observerParam['mutationCb']; private blockClass: observerParam['blockClass']; @@ -367,7 +368,7 @@ export default class MutationBuffer { for (const n of this.movedSet) { if ( - isParentRemoved(this.removes, n, this.mirror) && + isParentRemoved(this.removesSubTreeCache, n, this.mirror) && !this.movedSet.has(dom.parentNode(n)!) ) { continue; @@ -378,7 +379,7 @@ export default class MutationBuffer { for (const n of this.addedSet) { if ( !isAncestorInSet(this.droppedSet, n) && - !isParentRemoved(this.removes, n, this.mirror) + !isParentRemoved(this.removesSubTreeCache, n, this.mirror) ) { pushAdd(n); } else if (isAncestorInSet(this.movedSet, n)) { @@ -514,6 +515,7 @@ export default class MutationBuffer { this.addedSet = new Set(); this.movedSet = new Set(); this.droppedSet = new Set(); + this.removesSubTreeCache = new Set(); this.movedMap = {}; this.mutationCb(payload); @@ -740,6 +742,7 @@ export default class MutationBuffer { ? true : undefined, }); + processRemoves(n, this.removesSubTreeCache); } this.mapRemoves.push(n); }); @@ -803,29 +806,33 @@ function deepDelete(addsSet: Set, n: Node) { dom.childNodes(n).forEach((childN) => deepDelete(addsSet, childN)); } -function isParentRemoved( - removes: removedNodeMutation[], - n: Node, - mirror: Mirror, -): boolean { - if (removes.length === 0) return false; +function processRemoves(n: Node, cache: Set) { + const queue = [n]; + + while (queue.length) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const next = queue.pop()!; + if (cache.has(next)) continue; + cache.add(next); + dom.childNodes(next).forEach((n) => queue.push(n)); + } + + return; +} + +function isParentRemoved(removes: Set, n: Node, mirror: Mirror): boolean { + if (removes.size === 0) return false; return _isParentRemoved(removes, n, mirror); } function _isParentRemoved( - removes: removedNodeMutation[], + removes: Set, n: Node, - mirror: Mirror, + _mirror: Mirror, ): boolean { - let node: ParentNode | null = dom.parentNode(n); - while (node) { - const parentId = mirror.getId(node); - if (removes.some((r) => r.id === parentId)) { - return true; - } - node = dom.parentNode(node); - } - return false; + const node: ParentNode | null = dom.parentNode(n); + if (!node) return false; + return removes.has(node); } function isAncestorInSet(set: Set, n: Node): boolean {