From b4007df907530c4a5fe43f1365bce41fd5c43019 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Thu, 18 Jul 2024 16:07:21 -0400 Subject: [PATCH 1/4] perf: improve serialize attributes --- packages/rrweb/src/record/mutation.ts | 66 +++++++++++++++------------ 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts index a798441969..9d5554b7fd 100644 --- a/packages/rrweb/src/record/mutation.ts +++ b/packages/rrweb/src/record/mutation.ts @@ -19,6 +19,7 @@ import type { removedNodeMutation, addedNodeMutation, Optional, + mutationCallbackParam, } from '@rrweb/types'; import { isBlocked, @@ -133,6 +134,42 @@ class DoubleLinkedList { const moveKey = (id: number, parentId: number) => `${id}@${parentId}`; +function serializeAttributes(input: attributeCursor[], addedIds: Set): mutationCallbackParam['attributes'] { + const attributes = []; + for (let i = 0; i < input.length; i++) { + const id = this.mirror.getId(input[i].node); + if (addedIds.has(id) || !this.mirror.has(id)) { + continue; + } + + const { attributes: elAttributes } = input[i]; + if (typeof elAttributes.style === 'string') { + const diffAsStr = JSON.stringify(input[i].styleDiff); + const unchangedAsStr = JSON.stringify( + input[i]._unchangedStyles, + ); + // check if the style diff is actually shorter than the regular string based mutation + // (which was the whole point of #464 'compact style mutation'). + if (diffAsStr.length < elAttributes.style.length) { + // also: CSSOM fails badly when var() is present on shorthand properties, so only proceed with + // the compact style mutation if these have all been accounted for + if ( + (diffAsStr + unchangedAsStr).split('var(').length === + elAttributes.style.split('var(').length + ) { + elAttributes.style = input[i].styleDiff; + } + } + } + + attributes.push({ + id, + attributes: input[i].attributes, + }); + } + + return attributes +} /** * controls behaviour of a MutationObserver */ @@ -457,34 +494,7 @@ export default class MutationBuffer { .filter((text) => !addedIds.has(text.id)) // text mutation's id was not in the mirror map means the target node has been removed .filter((text) => this.mirror.has(text.id)), - attributes: this.attributes - .map((attribute) => { - const { attributes } = attribute; - if (typeof attributes.style === 'string') { - const diffAsStr = JSON.stringify(attribute.styleDiff); - const unchangedAsStr = JSON.stringify(attribute._unchangedStyles); - // check if the style diff is actually shorter than the regular string based mutation - // (which was the whole point of #464 'compact style mutation'). - if (diffAsStr.length < attributes.style.length) { - // also: CSSOM fails badly when var() is present on shorthand properties, so only proceed with - // the compact style mutation if these have all been accounted for - if ( - (diffAsStr + unchangedAsStr).split('var(').length === - attributes.style.split('var(').length - ) { - attributes.style = attribute.styleDiff; - } - } - } - return { - id: this.mirror.getId(attribute.node), - attributes: attributes, - }; - }) - // no need to include them on added elements, as they have just been serialized with up to date attribubtes - .filter((attribute) => !addedIds.has(attribute.id)) - // attribute mutation's id was not in the mirror map means the target node has been removed - .filter((attribute) => this.mirror.has(attribute.id)), + attributes: serializeAttributes(this.attributes, addedIds), removes: this.removes, adds, }; From ee7578133f66a41bd286e249490b303edc8369c2 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Thu, 18 Jul 2024 16:11:08 -0400 Subject: [PATCH 2/4] format --- packages/rrweb/src/record/mutation.ts | 59 ++++++++++++++------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts index 9d5554b7fd..d1736b3df4 100644 --- a/packages/rrweb/src/record/mutation.ts +++ b/packages/rrweb/src/record/mutation.ts @@ -134,41 +134,42 @@ class DoubleLinkedList { const moveKey = (id: number, parentId: number) => `${id}@${parentId}`; -function serializeAttributes(input: attributeCursor[], addedIds: Set): mutationCallbackParam['attributes'] { +function serializeAttributes( + input: attributeCursor[], + addedIds: Set, +): mutationCallbackParam['attributes'] { const attributes = []; - for (let i = 0; i < input.length; i++) { - const id = this.mirror.getId(input[i].node); - if (addedIds.has(id) || !this.mirror.has(id)) { - continue; - } + for (let i = 0; i < input.length; i++) { + const id = this.mirror.getId(input[i].node); + if (addedIds.has(id) || !this.mirror.has(id)) { + continue; + } - const { attributes: elAttributes } = input[i]; - if (typeof elAttributes.style === 'string') { - const diffAsStr = JSON.stringify(input[i].styleDiff); - const unchangedAsStr = JSON.stringify( - input[i]._unchangedStyles, - ); - // check if the style diff is actually shorter than the regular string based mutation - // (which was the whole point of #464 'compact style mutation'). - if (diffAsStr.length < elAttributes.style.length) { - // also: CSSOM fails badly when var() is present on shorthand properties, so only proceed with - // the compact style mutation if these have all been accounted for - if ( - (diffAsStr + unchangedAsStr).split('var(').length === - elAttributes.style.split('var(').length - ) { - elAttributes.style = input[i].styleDiff; - } + const { attributes: elAttributes } = input[i]; + if (typeof elAttributes.style === 'string') { + const diffAsStr = JSON.stringify(input[i].styleDiff); + const unchangedAsStr = JSON.stringify(input[i]._unchangedStyles); + // check if the style diff is actually shorter than the regular string based mutation + // (which was the whole point of #464 'compact style mutation'). + if (diffAsStr.length < elAttributes.style.length) { + // also: CSSOM fails badly when var() is present on shorthand properties, so only proceed with + // the compact style mutation if these have all been accounted for + if ( + (diffAsStr + unchangedAsStr).split('var(').length === + elAttributes.style.split('var(').length + ) { + elAttributes.style = input[i].styleDiff; } } - - attributes.push({ - id, - attributes: input[i].attributes, - }); } - return attributes + attributes.push({ + id, + attributes: input[i].attributes, + }); + } + + return attributes; } /** * controls behaviour of a MutationObserver From b9d5b42d960f809557535e1f1a93e02ab1d45e04 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Thu, 18 Jul 2024 17:11:59 -0400 Subject: [PATCH 3/4] format --- packages/rrweb/src/record/mutation.ts | 77 ++++++++++++++------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts index d1736b3df4..d384c1cf8f 100644 --- a/packages/rrweb/src/record/mutation.ts +++ b/packages/rrweb/src/record/mutation.ts @@ -134,43 +134,6 @@ class DoubleLinkedList { const moveKey = (id: number, parentId: number) => `${id}@${parentId}`; -function serializeAttributes( - input: attributeCursor[], - addedIds: Set, -): mutationCallbackParam['attributes'] { - const attributes = []; - for (let i = 0; i < input.length; i++) { - const id = this.mirror.getId(input[i].node); - if (addedIds.has(id) || !this.mirror.has(id)) { - continue; - } - - const { attributes: elAttributes } = input[i]; - if (typeof elAttributes.style === 'string') { - const diffAsStr = JSON.stringify(input[i].styleDiff); - const unchangedAsStr = JSON.stringify(input[i]._unchangedStyles); - // check if the style diff is actually shorter than the regular string based mutation - // (which was the whole point of #464 'compact style mutation'). - if (diffAsStr.length < elAttributes.style.length) { - // also: CSSOM fails badly when var() is present on shorthand properties, so only proceed with - // the compact style mutation if these have all been accounted for - if ( - (diffAsStr + unchangedAsStr).split('var(').length === - elAttributes.style.split('var(').length - ) { - elAttributes.style = input[i].styleDiff; - } - } - } - - attributes.push({ - id, - attributes: input[i].attributes, - }); - } - - return attributes; -} /** * controls behaviour of a MutationObserver */ @@ -297,6 +260,44 @@ export default class MutationBuffer { this.emit(); // clears buffer if not locked/frozen }; + private serializeAttributes( + input: attributeCursor[], + addedIds: Set, + ): mutationCallbackParam['attributes'] { + const attributes = []; + for (let i = 0; i < input.length; i++) { + const id = this.mirror.getId(input[i].node); + if (addedIds.has(id) || !this.mirror.has(id)) { + continue; + } + + const { attributes: elAttributes } = input[i]; + if (typeof elAttributes.style === 'string') { + const diffAsStr = JSON.stringify(input[i].styleDiff); + const unchangedAsStr = JSON.stringify(input[i]._unchangedStyles); + // check if the style diff is actually shorter than the regular string based mutation + // (which was the whole point of #464 'compact style mutation'). + if (diffAsStr.length < elAttributes.style.length) { + // also: CSSOM fails badly when var() is present on shorthand properties, so only proceed with + // the compact style mutation if these have all been accounted for + if ( + (diffAsStr + unchangedAsStr).split('var(').length === + elAttributes.style.split('var(').length + ) { + elAttributes.style = input[i].styleDiff; + } + } + } + + attributes.push({ + id, + attributes: input[i].attributes, + }); + } + + return attributes; + } + public emit = () => { if (this.frozen || this.locked) { return; @@ -495,7 +496,7 @@ export default class MutationBuffer { .filter((text) => !addedIds.has(text.id)) // text mutation's id was not in the mirror map means the target node has been removed .filter((text) => this.mirror.has(text.id)), - attributes: serializeAttributes(this.attributes, addedIds), + attributes: this.serializeAttributes(this.attributes, addedIds), removes: this.removes, adds, }; From e98ace8ed04532abdb190df5be58b8915dff35c4 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Thu, 18 Jul 2024 17:13:17 -0400 Subject: [PATCH 4/4] add changese --- .changeset/large-falcons-smash.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/large-falcons-smash.md diff --git a/.changeset/large-falcons-smash.md b/.changeset/large-falcons-smash.md new file mode 100644 index 0000000000..a110c3d754 --- /dev/null +++ b/.changeset/large-falcons-smash.md @@ -0,0 +1,5 @@ +--- +"rrweb": minor +--- + +improve perf of attribute serialization