Skip to content

Commit f27e545

Browse files
authored
Fix: Switch to real dom before rebuilding fullsnapshot (#1139)
1 parent d82c5ed commit f27e545

File tree

6 files changed

+61
-1
lines changed

6 files changed

+61
-1
lines changed

.changeset/eight-terms-hunt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'rrweb': patch
3+
---
4+
5+
Fix: Switch from virtual dom to real dom before rebuilding fullsnapshot

.changeset/real-masks-explode.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'rrdom': patch
3+
---
4+
5+
Fix: If RRNode appends a single child twice, children of the node will become an infinite link list.

packages/rrdom/src/diff.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ function diffChildren(
491491
} else if (newStartIndex > newEndIndex) {
492492
for (; oldStartIndex <= oldEndIndex; oldStartIndex++) {
493493
const node = oldChildren[oldStartIndex];
494-
if (!node || !parentNode.contains(node)) continue;
494+
if (!node || node.parentNode !== parentNode) continue;
495495
try {
496496
parentNode.removeChild(node);
497497
replayer.mirror.removeNodeFromMap(node);

packages/rrdom/src/document.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,8 @@ export type CSSStyleDeclaration = Record<string, string> & {
713713
};
714714

715715
function appendChild(parent: IRRNode, newChild: IRRNode) {
716+
if (newChild.parentNode) newChild.parentNode.removeChild(newChild);
717+
716718
if (parent.lastChild) {
717719
parent.lastChild.nextSibling = newChild;
718720
newChild.previousSibling = parent.lastChild;
@@ -740,6 +742,9 @@ function insertBefore(
740742
"Failed to execute 'insertBefore' on 'RRNode': The RRNode before which the new node is to be inserted is not a child of this RRNode.",
741743
);
742744

745+
if (newChild === refChild) return newChild;
746+
if (newChild.parentNode) newChild.parentNode.removeChild(newChild);
747+
743748
newChild.previousSibling = refChild.previousSibling;
744749
refChild.previousSibling = newChild;
745750
newChild.nextSibling = refChild;

packages/rrdom/test/document.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,22 @@ describe('Basic RRDocument implementation', () => {
766766
expect(node.contains(child2)).toBeTruthy();
767767
});
768768

769+
it('can append a child with parent node', () => {
770+
const node = document.createElement('div');
771+
const child = document.createElement('span');
772+
expect(node.appendChild(child)).toBe(child);
773+
expect(node.childNodes).toEqual([child]);
774+
expect(node.appendChild(child)).toBe(child);
775+
expect(node.childNodes).toEqual([child]);
776+
expect(child.parentNode).toBe(node);
777+
778+
const node1 = document.createElement('div');
779+
expect(node1.appendChild(child)).toBe(child);
780+
expect(node1.childNodes).toEqual([child]);
781+
expect(child.parentNode).toBe(node1);
782+
expect(node.childNodes).toEqual([]);
783+
});
784+
769785
it('can insert new child before an existing child', () => {
770786
const node = document.createElement('div');
771787
const child1 = document.createElement('h1');
@@ -820,6 +836,25 @@ describe('Basic RRDocument implementation', () => {
820836
expect(node.contains(child1)).toBeTruthy();
821837
});
822838

839+
it('can insert a child with parent node', () => {
840+
const node = document.createElement('div');
841+
const child1 = document.createElement('h1');
842+
expect(node.insertBefore(child1, null)).toBe(child1);
843+
expect(node.childNodes).toEqual([child1]);
844+
expect(node.insertBefore(child1, child1)).toBe(child1);
845+
expect(node.childNodes).toEqual([child1]);
846+
expect(child1.parentNode).toEqual(node);
847+
848+
const node2 = document.createElement('div');
849+
const child2 = document.createElement('h2');
850+
expect(node2.insertBefore(child2, null)).toBe(child2);
851+
expect(node2.childNodes).toEqual([child2]);
852+
expect(node2.insertBefore(child1, child2)).toBe(child1);
853+
expect(node2.childNodes).toEqual([child1, child2]);
854+
expect(child1.parentNode).toEqual(node2);
855+
expect(node.childNodes).toEqual([]);
856+
});
857+
823858
it('can remove an existing child', () => {
824859
const node = document.createElement('div');
825860
const child1 = document.createElement('h1');

packages/rrweb/src/replay/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,16 @@ export class Replayer {
777777
}
778778
};
779779

780+
/**
781+
* Normally rebuilding full snapshot should not be under virtual dom environment.
782+
* But if the order of data events has some issues, it might be possible.
783+
* Adding this check to avoid any potential issues.
784+
*/
785+
if (this.usingVirtualDom) {
786+
this.virtualDom.destroyTree();
787+
this.usingVirtualDom = false;
788+
}
789+
780790
this.mirror.reset();
781791
rebuild(event.data.node, {
782792
doc: this.iframe.contentDocument,

0 commit comments

Comments
 (0)