From 0d630e495b6241af6fe726cc1777837068cd70aa Mon Sep 17 00:00:00 2001 From: JonasBa Date: Mon, 7 Aug 2023 15:50:30 -0400 Subject: [PATCH 01/13] ref(genAdds): refactor to iterative --- packages/rrweb/src/record/mutation.ts | 76 ++++++++++++++++++--------- 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts index a798441969..9bae5caa4d 100644 --- a/packages/rrweb/src/record/mutation.ts +++ b/packages/rrweb/src/record/mutation.ts @@ -739,38 +739,64 @@ export default class MutationBuffer { /** * Make sure you check if `n`'s parent is blocked before calling this function * */ - private genAdds = (n: Node, target?: Node) => { - // this node was already recorded in other buffer, ignore it - if (this.processedNodeManager.inOtherBuffer(n, this)) return; + private genAddsQueue: [Node, Node | undefined][] = new Array< + [Node, Node | undefined] + >(1000); + private genAdds = (node: Node, t?: Node) => { + let rp = -1; + let wp = -1; + this.genAddsQueue[++wp] = [node, t]; + + while (rp < wp) { + const next = this.genAddsQueue[++rp]; + if (!next) { + throw new Error( + 'Add queue is corrupt, there is no next item to process', + ); + } + const [n, target] = next; - // if n is added to set, there is no need to travel it and its' children again - if (this.addedSet.has(n) || this.movedSet.has(n)) return; + // this node was already recorded in other buffer, ignore it + if (this.processedNodeManager.inOtherBuffer(n, this)) continue; - if (this.mirror.hasNode(n)) { - if (isIgnored(n, this.mirror, this.slimDOMOptions)) { - return; - } - this.movedSet.add(n); - let targetId: number | null = null; - if (target && this.mirror.hasNode(target)) { - targetId = this.mirror.getId(target); - } - if (targetId && targetId !== -1) { - this.movedMap[moveKey(this.mirror.getId(n), targetId)] = true; + // if n is added to set, there is no need to travel it and its' children again + if (this.addedSet.has(n) || this.movedSet.has(n)) continue; + + if (this.mirror.hasNode(n)) { + if (isIgnored(n, this.mirror, this.slimDOMOptions)) { + continue; + } + this.movedSet.add(n); + let targetId: number | null = null; + if (target && this.mirror.hasNode(target)) { + targetId = this.mirror.getId(target); + } + if (targetId && targetId !== -1) { + this.movedMap[moveKey(this.mirror.getId(n), targetId)] = true; + } + } else { + this.addedSet.add(n); + this.droppedSet.delete(n); } - } else { - this.addedSet.add(n); - this.droppedSet.delete(n); - } - // if this node is blocked `serializeNode` will turn it into a placeholder element - // but we have to remove it's children otherwise they will be added as placeholders too - if (!isBlocked(n, this.blockClass, this.blockSelector, false)) { - n.childNodes.forEach((childN) => this.genAdds(childN)); + const isNodeBlocked = isBlocked( + n, + this.blockClass, + this.blockSelector, + false, + ); + if (isNodeBlocked) { + return; + } + // if this node is blocked `serializeNode` will turn it into a placeholder element + // but we have to remove it's children otherwise they will be added as placeholders too + n.childNodes.forEach((childN) => { + this.genAddsQueue[++wp] = [childN, undefined]; + }); if (hasShadowRoot(n)) { n.shadowRoot.childNodes.forEach((childN) => { this.processedNodeManager.add(childN, this); - this.genAdds(childN, n); + this.genAddsQueue[++wp] = [childN, n]; }); } } From 82dab769209c771cfaf1e454ade4c03d461203a8 Mon Sep 17 00:00:00 2001 From: Eoghan Murray Date: Tue, 8 Aug 2023 09:54:50 +0100 Subject: [PATCH 02/13] `return` should have been converted to `continue` for the iterative version --- packages/rrweb/src/record/mutation.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts index 9bae5caa4d..c4723f3b0c 100644 --- a/packages/rrweb/src/record/mutation.ts +++ b/packages/rrweb/src/record/mutation.ts @@ -779,14 +779,8 @@ export default class MutationBuffer { this.droppedSet.delete(n); } - const isNodeBlocked = isBlocked( - n, - this.blockClass, - this.blockSelector, - false, - ); - if (isNodeBlocked) { - return; + if (isBlocked(n, this.blockClass, this.blockSelector, false)) { + continue; } // if this node is blocked `serializeNode` will turn it into a placeholder element // but we have to remove it's children otherwise they will be added as placeholders too From 337b05d64526b917a5d62f1fa69daa97d98e7c7e Mon Sep 17 00:00:00 2001 From: Eoghan Murray Date: Tue, 8 Aug 2023 10:05:59 +0100 Subject: [PATCH 03/13] No need for a separately named `genAdds` function if it's not recursive --- packages/rrweb/src/record/mutation.ts | 119 +++++++++++++------------- 1 file changed, 58 insertions(+), 61 deletions(-) diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts index c4723f3b0c..0e6faf4cdd 100644 --- a/packages/rrweb/src/record/mutation.ts +++ b/packages/rrweb/src/record/mutation.ts @@ -680,7 +680,64 @@ export default class MutationBuffer { return; // any removedNodes won't have been in mirror either } - m.addedNodes.forEach((n) => this.genAdds(n, m.target)); + const genAddsQueue: [Node, Node | undefined][] = new Array< + [Node, Node | undefined] + >(1000); + m.addedNodes.forEach((n) => { + let rp = -1; + let wp = -1; + genAddsQueue[++wp] = [n, m.target]; + + // iterate breadth first over new nodes (non recursive for performance) + while (rp < wp) { + const next = genAddsQueue[++rp]; + if (!next) { + throw new Error( + 'Add queue is corrupt, there is no next item to process', + ); + } + const [n, target] = next; + + // this node was already recorded in other buffer, ignore it + if (this.processedNodeManager.inOtherBuffer(n, this)) continue; + + // if n is added to set, there is no need to travel it and its' children again + if (this.addedSet.has(n) || this.movedSet.has(n)) continue; + + if (this.mirror.hasNode(n)) { + if (isIgnored(n, this.mirror, this.slimDOMOptions)) { + continue; + } + this.movedSet.add(n); + let targetId: number | null = null; + if (target && this.mirror.hasNode(target)) { + targetId = this.mirror.getId(target); + } + if (targetId && targetId !== -1) { + this.movedMap[moveKey(this.mirror.getId(n), targetId)] = true; + } + } else { + this.addedSet.add(n); + this.droppedSet.delete(n); + } + + if (isBlocked(n, this.blockClass, this.blockSelector, false)) { + // if this node is blocked `serializeNode` will turn it into a placeholder element + // but we have to ignore it's children otherwise they will be added as placeholders too + continue; + } + n.childNodes.forEach((childN) => { + genAddsQueue[++wp] = [childN, undefined]; + }); + if (hasShadowRoot(n)) { + n.shadowRoot.childNodes.forEach((childN) => { + this.processedNodeManager.add(childN, this); + genAddsQueue[++wp] = [childN, n]; + }); + } + } + }); + m.removedNodes.forEach((n) => { const nodeId = this.mirror.getId(n); const parentId = isShadowRoot(m.target) @@ -735,66 +792,6 @@ export default class MutationBuffer { break; } }; - - /** - * Make sure you check if `n`'s parent is blocked before calling this function - * */ - private genAddsQueue: [Node, Node | undefined][] = new Array< - [Node, Node | undefined] - >(1000); - private genAdds = (node: Node, t?: Node) => { - let rp = -1; - let wp = -1; - this.genAddsQueue[++wp] = [node, t]; - - while (rp < wp) { - const next = this.genAddsQueue[++rp]; - if (!next) { - throw new Error( - 'Add queue is corrupt, there is no next item to process', - ); - } - const [n, target] = next; - - // this node was already recorded in other buffer, ignore it - if (this.processedNodeManager.inOtherBuffer(n, this)) continue; - - // if n is added to set, there is no need to travel it and its' children again - if (this.addedSet.has(n) || this.movedSet.has(n)) continue; - - if (this.mirror.hasNode(n)) { - if (isIgnored(n, this.mirror, this.slimDOMOptions)) { - continue; - } - this.movedSet.add(n); - let targetId: number | null = null; - if (target && this.mirror.hasNode(target)) { - targetId = this.mirror.getId(target); - } - if (targetId && targetId !== -1) { - this.movedMap[moveKey(this.mirror.getId(n), targetId)] = true; - } - } else { - this.addedSet.add(n); - this.droppedSet.delete(n); - } - - if (isBlocked(n, this.blockClass, this.blockSelector, false)) { - continue; - } - // if this node is blocked `serializeNode` will turn it into a placeholder element - // but we have to remove it's children otherwise they will be added as placeholders too - n.childNodes.forEach((childN) => { - this.genAddsQueue[++wp] = [childN, undefined]; - }); - if (hasShadowRoot(n)) { - n.shadowRoot.childNodes.forEach((childN) => { - this.processedNodeManager.add(childN, this); - this.genAddsQueue[++wp] = [childN, n]; - }); - } - } - }; } /** From deeb233fe79d6709465d2eb77de715edb57e10c2 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Thu, 17 Aug 2023 09:14:43 -0400 Subject: [PATCH 04/13] ref(mutation): use arr push --- packages/rrweb/src/record/mutation.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts index 0e6faf4cdd..ece5b03792 100644 --- a/packages/rrweb/src/record/mutation.ts +++ b/packages/rrweb/src/record/mutation.ts @@ -682,21 +682,23 @@ export default class MutationBuffer { const genAddsQueue: [Node, Node | undefined][] = new Array< [Node, Node | undefined] - >(1000); + >(); m.addedNodes.forEach((n) => { - let rp = -1; - let wp = -1; - genAddsQueue[++wp] = [n, m.target]; + genAddsQueue.push([n, m.target]); // iterate breadth first over new nodes (non recursive for performance) - while (rp < wp) { - const next = genAddsQueue[++rp]; + while (genAddsQueue.length) { + const next = genAddsQueue.pop(); if (!next) { throw new Error( 'Add queue is corrupt, there is no next item to process', ); } - const [n, target] = next; + + // Since this is an extremely hot path, do not destructure next + // in order to avoid invoking the iterator protocol + const n = next[0]; + const target = next[1]; // this node was already recorded in other buffer, ignore it if (this.processedNodeManager.inOtherBuffer(n, this)) continue; @@ -727,12 +729,12 @@ export default class MutationBuffer { continue; } n.childNodes.forEach((childN) => { - genAddsQueue[++wp] = [childN, undefined]; + genAddsQueue.push([childN, undefined]); }); if (hasShadowRoot(n)) { n.shadowRoot.childNodes.forEach((childN) => { this.processedNodeManager.add(childN, this); - genAddsQueue[++wp] = [childN, n]; + genAddsQueue.push([childN, n]); }); } } From d23ce7549db429ab25ccbbd11753859b6fc3a502 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Thu, 17 Aug 2023 09:22:46 -0400 Subject: [PATCH 05/13] ref(mutation): update snapshot --- .../__snapshots__/integration.test.ts.snap | 269 +++++++++--------- 1 file changed, 139 insertions(+), 130 deletions(-) diff --git a/packages/rrweb/test/__snapshots__/integration.test.ts.snap b/packages/rrweb/test/__snapshots__/integration.test.ts.snap index 032bdbec63..2acfd3c164 100644 --- a/packages/rrweb/test/__snapshots__/integration.test.ts.snap +++ b/packages/rrweb/test/__snapshots__/integration.test.ts.snap @@ -3218,34 +3218,38 @@ exports[`record integration tests > can record node mutations 1`] = ` }, { \\"parentId\\": 26, - \\"nextId\\": 28, + \\"nextId\\": null, \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 27 + \\"type\\": 2, + \\"tagName\\": \\"span\\", + \\"attributes\\": { + \\"class\\": \\"select2-arrow\\", + \\"role\\": \\"presentation\\" + }, + \\"childNodes\\": [], + \\"id\\": 32 } }, { - \\"parentId\\": 26, - \\"nextId\\": 30, + \\"parentId\\": 32, + \\"nextId\\": null, \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"span\\", + \\"tagName\\": \\"b\\", \\"attributes\\": { - \\"class\\": \\"select2-chosen\\", - \\"id\\": \\"select2-chosen-1\\" + \\"role\\": \\"presentation\\" }, \\"childNodes\\": [], - \\"id\\": 28 + \\"id\\": 33 } }, { - \\"parentId\\": 28, - \\"nextId\\": null, + \\"parentId\\": 26, + \\"nextId\\": 32, \\"node\\": { \\"type\\": 3, - \\"textContent\\": \\"A\\", - \\"id\\": 29 + \\"textContent\\": \\" \\", + \\"id\\": 31 } }, { @@ -3263,38 +3267,34 @@ exports[`record integration tests > can record node mutations 1`] = ` }, { \\"parentId\\": 26, - \\"nextId\\": 32, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 31 - } - }, - { - \\"parentId\\": 26, - \\"nextId\\": null, + \\"nextId\\": 30, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"span\\", \\"attributes\\": { - \\"class\\": \\"select2-arrow\\", - \\"role\\": \\"presentation\\" + \\"class\\": \\"select2-chosen\\", + \\"id\\": \\"select2-chosen-1\\" }, \\"childNodes\\": [], - \\"id\\": 32 + \\"id\\": 28 } }, { - \\"parentId\\": 32, + \\"parentId\\": 28, \\"nextId\\": null, \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"b\\", - \\"attributes\\": { - \\"role\\": \\"presentation\\" - }, - \\"childNodes\\": [], - \\"id\\": 33 + \\"type\\": 3, + \\"textContent\\": \\"A\\", + \\"id\\": 29 + } + }, + { + \\"parentId\\": 26, + \\"nextId\\": 28, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\" \\", + \\"id\\": 27 } }, { @@ -3314,56 +3314,48 @@ exports[`record integration tests > can record node mutations 1`] = ` }, { \\"parentId\\": 36, - \\"nextId\\": 38, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 37 - } - }, - { - \\"parentId\\": 36, - \\"nextId\\": 44, + \\"nextId\\": null, \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"div\\", + \\"tagName\\": \\"ul\\", \\"attributes\\": { - \\"class\\": \\"select2-search\\" + \\"class\\": \\"select2-results\\", + \\"role\\": \\"listbox\\", + \\"id\\": \\"select2-results-1\\" }, \\"childNodes\\": [], - \\"id\\": 38 + \\"id\\": 45 } }, { - \\"parentId\\": 38, - \\"nextId\\": 40, + \\"parentId\\": 36, + \\"nextId\\": 45, \\"node\\": { \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 39 + \\"textContent\\": \\" \\", + \\"id\\": 44 } }, { - \\"parentId\\": 38, - \\"nextId\\": 41, + \\"parentId\\": 36, + \\"nextId\\": 44, \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"label\\", + \\"tagName\\": \\"div\\", \\"attributes\\": { - \\"for\\": \\"s2id_autogen1_search\\", - \\"class\\": \\"select2-offscreen\\" + \\"class\\": \\"select2-search\\" }, \\"childNodes\\": [], - \\"id\\": 40 + \\"id\\": 38 } }, { \\"parentId\\": 38, - \\"nextId\\": 42, + \\"nextId\\": null, \\"node\\": { \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 41 + \\"textContent\\": \\" \\", + \\"id\\": 43 } }, { @@ -3393,35 +3385,43 @@ exports[`record integration tests > can record node mutations 1`] = ` }, { \\"parentId\\": 38, - \\"nextId\\": null, + \\"nextId\\": 42, \\"node\\": { \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 43 + \\"textContent\\": \\" \\", + \\"id\\": 41 } }, { - \\"parentId\\": 36, - \\"nextId\\": 45, + \\"parentId\\": 38, + \\"nextId\\": 41, + \\"node\\": { + \\"type\\": 2, + \\"tagName\\": \\"label\\", + \\"attributes\\": { + \\"for\\": \\"s2id_autogen1_search\\", + \\"class\\": \\"select2-offscreen\\" + }, + \\"childNodes\\": [], + \\"id\\": 40 + } + }, + { + \\"parentId\\": 38, + \\"nextId\\": 40, \\"node\\": { \\"type\\": 3, - \\"textContent\\": \\" \\", - \\"id\\": 44 + \\"textContent\\": \\" \\", + \\"id\\": 39 } }, { \\"parentId\\": 36, - \\"nextId\\": null, + \\"nextId\\": 38, \\"node\\": { - \\"type\\": 2, - \\"tagName\\": \\"ul\\", - \\"attributes\\": { - \\"class\\": \\"select2-results\\", - \\"role\\": \\"listbox\\", - \\"id\\": \\"select2-results-1\\" - }, - \\"childNodes\\": [], - \\"id\\": 45 + \\"type\\": 3, + \\"textContent\\": \\" \\", + \\"id\\": 37 } }, { @@ -3463,29 +3463,18 @@ exports[`record integration tests > can record node mutations 1`] = ` } }, { - \\"parentId\\": 18, - \\"nextId\\": 36, + \\"parentId\\": 68, + \\"nextId\\": 69, \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"div\\", + \\"tagName\\": \\"span\\", \\"attributes\\": { - \\"id\\": \\"select2-drop-mask\\", - \\"class\\": \\"select2-drop-mask\\", - \\"style\\": \\"\\" + \\"class\\": \\"select2-match\\" }, \\"childNodes\\": [], \\"id\\": 70 } }, - { - \\"parentId\\": 62, - \\"nextId\\": null, - \\"node\\": { - \\"type\\": 3, - \\"textContent\\": \\"2 results are available, use up and down arrow keys to navigate.\\", - \\"id\\": 71 - } - }, { \\"parentId\\": 45, \\"nextId\\": 67, @@ -3497,11 +3486,11 @@ exports[`record integration tests > can record node mutations 1`] = ` \\"role\\": \\"presentation\\" }, \\"childNodes\\": [], - \\"id\\": 72 + \\"id\\": 71 } }, { - \\"parentId\\": 72, + \\"parentId\\": 71, \\"nextId\\": null, \\"node\\": { \\"type\\": 2, @@ -3512,21 +3501,21 @@ exports[`record integration tests > can record node mutations 1`] = ` \\"role\\": \\"option\\" }, \\"childNodes\\": [], - \\"id\\": 73 + \\"id\\": 72 } }, { - \\"parentId\\": 73, + \\"parentId\\": 72, \\"nextId\\": null, \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"A\\", - \\"id\\": 74 + \\"id\\": 73 } }, { - \\"parentId\\": 73, - \\"nextId\\": 74, + \\"parentId\\": 72, + \\"nextId\\": 73, \\"node\\": { \\"type\\": 2, \\"tagName\\": \\"span\\", @@ -3534,19 +3523,30 @@ exports[`record integration tests > can record node mutations 1`] = ` \\"class\\": \\"select2-match\\" }, \\"childNodes\\": [], - \\"id\\": 75 + \\"id\\": 74 } }, { - \\"parentId\\": 68, - \\"nextId\\": 69, + \\"parentId\\": 18, + \\"nextId\\": 36, \\"node\\": { \\"type\\": 2, - \\"tagName\\": \\"span\\", + \\"tagName\\": \\"div\\", \\"attributes\\": { - \\"class\\": \\"select2-match\\" + \\"id\\": \\"select2-drop-mask\\", + \\"class\\": \\"select2-drop-mask\\", + \\"style\\": \\"\\" }, \\"childNodes\\": [], + \\"id\\": 75 + } + }, + { + \\"parentId\\": 62, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"2 results are available, use up and down arrow keys to navigate.\\", \\"id\\": 76 } } @@ -3576,7 +3576,7 @@ exports[`record integration tests > can record node mutations 1`] = ` \\"data\\": { \\"source\\": 2, \\"type\\": 0, - \\"id\\": 70 + \\"id\\": 75 } }, { @@ -9585,6 +9585,15 @@ exports[`record integration tests > should not record input values if dynamicall \\"id\\": 21 } }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 3, + \\"id\\": 21, + \\"x\\": 2, + \\"y\\": 0 + } + }, { \\"type\\": 3, \\"data\\": { @@ -10689,11 +10698,11 @@ exports[`record integration tests > should record DOM node movement 1 1`] = ` }, { \\"parentId\\": 12, - \\"nextId\\": 14, + \\"nextId\\": null, \\"node\\": { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 13 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 19 } }, { @@ -10709,11 +10718,11 @@ exports[`record integration tests > should record DOM node movement 1 1`] = ` }, { \\"parentId\\": 14, - \\"nextId\\": 16, + \\"nextId\\": null, \\"node\\": { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 15 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 18 } }, { @@ -10738,20 +10747,20 @@ exports[`record integration tests > should record DOM node movement 1 1`] = ` }, { \\"parentId\\": 14, - \\"nextId\\": null, + \\"nextId\\": 16, \\"node\\": { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 18 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 15 } }, { \\"parentId\\": 12, - \\"nextId\\": null, + \\"nextId\\": 14, \\"node\\": { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 19 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 13 } } ] @@ -10945,11 +10954,11 @@ exports[`record integration tests > should record DOM node movement 2 1`] = ` \\"adds\\": [ { \\"parentId\\": 12, - \\"nextId\\": 14, + \\"nextId\\": null, \\"node\\": { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 13 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 19 } }, { @@ -10965,11 +10974,11 @@ exports[`record integration tests > should record DOM node movement 2 1`] = ` }, { \\"parentId\\": 14, - \\"nextId\\": 16, + \\"nextId\\": null, \\"node\\": { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 15 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 18 } }, { @@ -10994,20 +11003,20 @@ exports[`record integration tests > should record DOM node movement 2 1`] = ` }, { \\"parentId\\": 14, - \\"nextId\\": null, + \\"nextId\\": 16, \\"node\\": { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 18 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 15 } }, { \\"parentId\\": 12, - \\"nextId\\": null, + \\"nextId\\": 14, \\"node\\": { \\"type\\": 3, - \\"textContent\\": \\"\\\\n \\", - \\"id\\": 19 + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 13 } }, { From 30548a24182d4a6334fbd79688901f3512a0c0b5 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Fri, 24 May 2024 13:24:24 -0400 Subject: [PATCH 06/13] perf(mutation): dont reschedule nodes if they exist in the set --- packages/rrweb/src/record/mutation.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts index ece5b03792..9a74374f61 100644 --- a/packages/rrweb/src/record/mutation.ts +++ b/packages/rrweb/src/record/mutation.ts @@ -688,12 +688,7 @@ export default class MutationBuffer { // iterate breadth first over new nodes (non recursive for performance) while (genAddsQueue.length) { - const next = genAddsQueue.pop(); - if (!next) { - throw new Error( - 'Add queue is corrupt, there is no next item to process', - ); - } + const next = genAddsQueue.pop()!; // Since this is an extremely hot path, do not destructure next // in order to avoid invoking the iterator protocol @@ -729,10 +724,16 @@ export default class MutationBuffer { continue; } n.childNodes.forEach((childN) => { + if (this.movedSet.has(childN) || this.addedSet.has(childN)) + return; + genAddsQueue.push([childN, undefined]); }); if (hasShadowRoot(n)) { n.shadowRoot.childNodes.forEach((childN) => { + if (this.movedSet.has(childN) || this.addedSet.has(childN)) + return; + this.processedNodeManager.add(childN, this); genAddsQueue.push([childN, n]); }); From 2b8de82f9bbe04819b230189ac70bb2a71270823 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Fri, 24 May 2024 13:39:12 -0400 Subject: [PATCH 07/13] eoghan: add way to call deep recursive test (already added in #1489) --- packages/rrweb/package.json | 1 + .../rrweb/test/html/benchmark-dom-mutation-deep-nested.html | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/rrweb/package.json b/packages/rrweb/package.json index 8c7a81e102..916e20a5f3 100644 --- a/packages/rrweb/package.json +++ b/packages/rrweb/package.json @@ -20,6 +20,7 @@ "check-types": "tsc -noEmit", "prepublish": "tsc -noEmit && vite build", "lint": "yarn eslint src", + "benchmark-dom-mutation": "vitest run --maxConcurrency 1 --no-file-parallelism -t 'deeply nested children' test/benchmark/dom-mutation" "benchmark": "vitest run --maxConcurrency 1 --no-file-parallelism test/benchmark" }, "type": "module", diff --git a/packages/rrweb/test/html/benchmark-dom-mutation-deep-nested.html b/packages/rrweb/test/html/benchmark-dom-mutation-deep-nested.html index fd0a4258b2..3c448dc6ed 100644 --- a/packages/rrweb/test/html/benchmark-dom-mutation-deep-nested.html +++ b/packages/rrweb/test/html/benchmark-dom-mutation-deep-nested.html @@ -2,7 +2,7 @@ + `); + await page.setContent(getHtml.call(this, suite.html)); } else { await page.goto(suite.url); } - - await addRecordingScript(page); }; const getDuration = async (): Promise => { From 605903841e9acea10cc70e4710bb65e2453521cc Mon Sep 17 00:00:00 2001 From: JonasBa Date: Thu, 18 Jul 2024 14:29:22 -0400 Subject: [PATCH 10/13] run prettier --- packages/rrweb/test/benchmark/dom-mutation.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/rrweb/test/benchmark/dom-mutation.test.ts b/packages/rrweb/test/benchmark/dom-mutation.test.ts index 42727b3ed6..249d92a728 100644 --- a/packages/rrweb/test/benchmark/dom-mutation.test.ts +++ b/packages/rrweb/test/benchmark/dom-mutation.test.ts @@ -99,7 +99,9 @@ describe('benchmark: mutation observer', () => { const loadPage = async () => { if ('html' in suite) { await page.goto('about:blank'); - const code = fs.readFileSync(path.resolve(__dirname, '../../dist/rrweb.umd.cjs')).toString() + const code = fs + .readFileSync(path.resolve(__dirname, '../../dist/rrweb.umd.cjs')) + .toString(); await page.setContent(`