Skip to content

Commit

Permalink
Merge branch 'master' into jb/perf/replay
Browse files Browse the repository at this point in the history
  • Loading branch information
eoghanmurray authored Aug 3, 2023
2 parents 58ab15c + 7103625 commit 74c80b1
Show file tree
Hide file tree
Showing 10 changed files with 682 additions and 123 deletions.
5 changes: 5 additions & 0 deletions .changeset/attribute-text-reductions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'rrweb': patch
---

Don't include redundant data from text/attribute mutations on just-added nodes
9 changes: 9 additions & 0 deletions .changeset/clean-plants-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'rrweb': patch
'@rrweb/types': patch
---

Compact style mutation fixes and improvements

- fixes when style updates contain a 'var()' on a shorthand property #1246
- further ensures that style mutations are compact by reverting to string method if it is shorter
123 changes: 74 additions & 49 deletions packages/rrweb/src/record/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import type {
attributeCursor,
removedNodeMutation,
addedNodeMutation,
styleAttributeValue,
Optional,
} from '@rrweb/types';
import {
Expand Down Expand Up @@ -324,6 +323,7 @@ export default class MutationBuffer {
nextId,
node: sn,
});
addedIds.add(sn.id);
}
}

Expand All @@ -336,6 +336,7 @@ export default class MutationBuffer {
// so that the mirror for takeFullSnapshot doesn't get mutated while it's event is being processed

const adds: addedNodeMutation[] = [];
const addedIds = new Set<number>();

/**
* Sometimes child node may be pushed before its newly added
Expand Down Expand Up @@ -436,32 +437,58 @@ export default class MutationBuffer {

const texts = [];
for (let i = 0; i < this.texts.length; i++) {
if (!this.mirror.getId(this.texts[i].node)) {
const id = this.mirror.getId(this.texts[i].node);
// text mutation's id was not in the mirror map means the target node has been removed from the DOM
if (!id && id !== 0) {
continue;
}
// no need to include mutations on newly added elements, as they have just been serialized with up to date texts
if (addedIds.has(id)) {
continue;
}
texts.push({
id: this.mirror.getId(this.texts[i].node),
id,
value: this.texts[i].value,
});
}

const attributes = [];
for (let i = 0; i < this.attributes.length; i++) {
const id = this.mirror.getId(this.attributes[i].node);
const attr = this.attributes[i];
const id = this.mirror.getId(attr.node);
// attribute mutation's id was not in the mirror map means the target node has been removed from the DOM
if (!id && id !== 0) {
continue;
}
// no need to include mutations on newly added elements, as they have just been serialized with up to date attributes
if (addedIds.has(id)) {
continue;
}
if (typeof attr.attributes.style === 'string') {
const diffAsStr = JSON.stringify(attr.styleDiff);
const unchangedAsStr = JSON.stringify(attr._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 < attr.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 ===
attr.attributes.style.split('var(').length
) {
attr.attributes.style = attr.styleDiff;
}
}
}
attributes.push({
id,
attributes: this.attributes[i].attributes,
attributes: attr.attributes,
});
}

const payload = {
texts,
// text mutation's id was not in the mirror map means the target node has been removed
attributes,
// attribute mutation's id was not in the mirror map means the target node has been removed
removes: this.removes,
adds,
};
Expand Down Expand Up @@ -559,6 +586,8 @@ export default class MutationBuffer {
item = {
node: m.target,
attributes: {},
styleDiff: {},
_unchangedStyles: {},
};
this.attributes.push(item);
}
Expand All @@ -572,55 +601,51 @@ export default class MutationBuffer {
) {
target.setAttribute('data-rr-is-password', 'true');
}

if (attributeName === 'style') {
let unattachedDoc;
try {
// avoid upsetting original document from a Content Security point of view
unattachedDoc = document.implementation.createHTMLDocument();
} catch (e) {
// fallback to more direct method
unattachedDoc = this.doc;
}
const old = unattachedDoc.createElement('span');
if (m.oldValue) {
old.setAttribute('style', m.oldValue);
}
if (
item.attributes.style === undefined ||
item.attributes.style === null
) {
item.attributes.style = {};
}
const styleObj = item.attributes.style as styleAttributeValue;
for (const pname of Array.from(target.style)) {
const newValue = target.style.getPropertyValue(pname);
const newPriority = target.style.getPropertyPriority(pname);
if (
newValue !== old.style.getPropertyValue(pname) ||
newPriority !== old.style.getPropertyPriority(pname)
) {
if (newPriority === '') {
styleObj[pname] = newValue;
} else {
styleObj[pname] = [newValue, newPriority];
}
}
}
for (const pname of Array.from(old.style)) {
if (target.style.getPropertyValue(pname) === '') {
// "if not set, returns the empty string"
styleObj[pname] = false; // delete
}
}
} else if (!ignoreAttribute(target.tagName, attributeName, value)) {
if (!ignoreAttribute(target.tagName, attributeName, value)) {
// overwrite attribute if the mutations was triggered in same time
item.attributes[attributeName] = transformAttribute(
this.doc,
toLowerCase(target.tagName),
toLowerCase(attributeName),
value,
);
if (attributeName === 'style') {
let unattachedDoc;
try {
// avoid upsetting original document from a Content Security point of view
unattachedDoc = document.implementation.createHTMLDocument();
} catch (e) {
// fallback to more direct method
unattachedDoc = this.doc;
}
const old = unattachedDoc.createElement('span');
if (m.oldValue) {
old.setAttribute('style', m.oldValue);
}
for (const pname of Array.from(target.style)) {
const newValue = target.style.getPropertyValue(pname);
const newPriority = target.style.getPropertyPriority(pname);
if (
newValue !== old.style.getPropertyValue(pname) ||
newPriority !== old.style.getPropertyPriority(pname)
) {
if (newPriority === '') {
item.styleDiff[pname] = newValue;
} else {
item.styleDiff[pname] = [newValue, newPriority];
}
} else {
// for checking
item._unchangedStyles[pname] = [newValue, newPriority];
}
}
for (const pname of Array.from(old.style)) {
if (target.style.getPropertyValue(pname) === '') {
// "if not set, returns the empty string"
item.styleDiff[pname] = false; // delete
}
}
}
}
break;
}
Expand Down
Loading

0 comments on commit 74c80b1

Please sign in to comment.