improve rrdom performance (#1127)
* add more check to rrdom to make diff algorithm more robust * fix: selector match in iframe is case-insensitive add try catch to some fragile points * test: increase timeout value for Jest * improve code style * fix: failed to execute insertBefore on Node in the diff function this happens when ids of doctype or html element are changed in the virtual dom also improve the code quality * refactor diff function to make the code cleaner * fix: virtual nodes are passed to plugin's onBuild function * refactor the diff function and adjust the order of diff work. * call afterAppend hook in a consistent traversal order * improve the performance of the "contains" function reduce the complexity from O(n) to O(logn) a specific benchmark is needed to add further * add a real events for benchmark * refactor: change the data structure of childNodes from array to linked list * remove legacy code in rrweb package * update unit tests * update change log
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
BaseRRMediaElementImpl,
|
||||
BaseRRNode,
|
||||
IRRDocumentType,
|
||||
IRRNode,
|
||||
} from '../src/document';
|
||||
|
||||
describe('Basic RRDocument implementation', () => {
|
||||
@@ -34,6 +35,7 @@ describe('Basic RRDocument implementation', () => {
|
||||
expect(node.TEXT_NODE).toBe(document.TEXT_NODE);
|
||||
expect(node.firstChild).toBeNull();
|
||||
expect(node.lastChild).toBeNull();
|
||||
expect(node.previousSibling).toBeNull();
|
||||
expect(node.nextSibling).toBeNull();
|
||||
expect(node.contains).toBeDefined();
|
||||
expect(node.appendChild).toBeDefined();
|
||||
@@ -42,56 +44,103 @@ describe('Basic RRDocument implementation', () => {
|
||||
expect(node.toString()).toEqual('RRNode');
|
||||
});
|
||||
|
||||
it('can get first child node', () => {
|
||||
it('can get and set first child node', () => {
|
||||
const parentNode = new RRNode();
|
||||
const childNode1 = new RRNode();
|
||||
const childNode2 = new RRNode();
|
||||
expect(parentNode.firstChild).toBeNull();
|
||||
parentNode.childNodes = [childNode1];
|
||||
parentNode.firstChild = childNode1;
|
||||
expect(parentNode.firstChild).toBe(childNode1);
|
||||
parentNode.childNodes = [childNode1, childNode2];
|
||||
expect(parentNode.firstChild).toBe(childNode1);
|
||||
parentNode.childNodes = [childNode2, childNode1];
|
||||
expect(parentNode.firstChild).toBe(childNode2);
|
||||
parentNode.firstChild = null;
|
||||
expect(parentNode.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('can get last child node', () => {
|
||||
it('can get and set last child node', () => {
|
||||
const parentNode = new RRNode();
|
||||
const childNode1 = new RRNode();
|
||||
const childNode2 = new RRNode();
|
||||
expect(parentNode.lastChild).toBeNull();
|
||||
parentNode.childNodes = [childNode1];
|
||||
expect(parentNode.lastChild).toBe(childNode1);
|
||||
parentNode.childNodes = [childNode1, childNode2];
|
||||
expect(parentNode.lastChild).toBe(childNode2);
|
||||
parentNode.childNodes = [childNode2, childNode1];
|
||||
parentNode.lastChild = childNode1;
|
||||
expect(parentNode.lastChild).toBe(childNode1);
|
||||
parentNode.lastChild = null;
|
||||
expect(parentNode.lastChild).toBeNull();
|
||||
});
|
||||
|
||||
it('can get nextSibling', () => {
|
||||
it('can get and set preSibling', () => {
|
||||
const node1 = new RRNode();
|
||||
const node2 = new RRNode();
|
||||
expect(node1.previousSibling).toBeNull();
|
||||
node1.previousSibling = node2;
|
||||
expect(node1.previousSibling).toBe(node2);
|
||||
node1.previousSibling = null;
|
||||
expect(node1.previousSibling).toBeNull();
|
||||
});
|
||||
|
||||
it('can get and set nextSibling', () => {
|
||||
const node1 = new RRNode();
|
||||
const node2 = new RRNode();
|
||||
expect(node1.nextSibling).toBeNull();
|
||||
node1.nextSibling = node2;
|
||||
expect(node1.nextSibling).toBe(node2);
|
||||
node1.nextSibling = null;
|
||||
expect(node1.nextSibling).toBeNull();
|
||||
});
|
||||
|
||||
it('can get childNodes', () => {
|
||||
const parentNode = new RRNode();
|
||||
expect(parentNode.childNodes).toBeInstanceOf(Array);
|
||||
expect(parentNode.childNodes.length).toBe(0);
|
||||
|
||||
const childNode1 = new RRNode();
|
||||
parentNode.firstChild = childNode1;
|
||||
parentNode.lastChild = childNode1;
|
||||
expect(parentNode.childNodes).toEqual([childNode1]);
|
||||
|
||||
const childNode2 = new RRNode();
|
||||
expect(parentNode.nextSibling).toBeNull();
|
||||
expect(childNode1.nextSibling).toBeNull();
|
||||
childNode1.parentNode = parentNode;
|
||||
parentNode.childNodes = [childNode1];
|
||||
expect(childNode1.nextSibling).toBeNull();
|
||||
childNode2.parentNode = parentNode;
|
||||
parentNode.childNodes = [childNode1, childNode2];
|
||||
expect(childNode1.nextSibling).toBe(childNode2);
|
||||
expect(childNode2.nextSibling).toBeNull();
|
||||
parentNode.lastChild = childNode2;
|
||||
childNode1.nextSibling = childNode2;
|
||||
childNode2.previousSibling = childNode1;
|
||||
expect(parentNode.childNodes).toEqual([childNode1, childNode2]);
|
||||
|
||||
const childNode3 = new RRNode();
|
||||
parentNode.lastChild = childNode3;
|
||||
childNode2.nextSibling = childNode3;
|
||||
childNode3.previousSibling = childNode2;
|
||||
expect(parentNode.childNodes).toEqual([
|
||||
childNode1,
|
||||
childNode2,
|
||||
childNode3,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return whether the node contains another node', () => {
|
||||
const parentNode = new RRNode();
|
||||
expect(parentNode.contains(parentNode)).toBeTruthy();
|
||||
expect(parentNode.contains(null as unknown as IRRNode)).toBeFalsy();
|
||||
expect(parentNode.contains(undefined as unknown as IRRNode)).toBeFalsy();
|
||||
expect(parentNode.contains({} as unknown as IRRNode)).toBeFalsy();
|
||||
expect(
|
||||
parentNode.contains(new RRDocument().createElement('div')),
|
||||
).toBeFalsy();
|
||||
const childNode1 = new RRNode();
|
||||
const childNode2 = new RRNode();
|
||||
parentNode.childNodes = [childNode1];
|
||||
parentNode.firstChild = childNode1;
|
||||
parentNode.lastChild = childNode1;
|
||||
childNode1.parentNode = parentNode;
|
||||
expect(parentNode.contains(childNode1)).toBeTruthy();
|
||||
expect(parentNode.contains(childNode2)).toBeFalsy();
|
||||
childNode1.childNodes = [childNode2];
|
||||
|
||||
parentNode.lastChild = childNode2;
|
||||
childNode1.nextSibling = childNode2;
|
||||
childNode2.previousSibling = childNode1;
|
||||
childNode2.parentNode = childNode1;
|
||||
expect(parentNode.contains(childNode1)).toBeTruthy();
|
||||
expect(parentNode.contains(childNode2)).toBeTruthy();
|
||||
|
||||
const childNode3 = new RRNode();
|
||||
expect(parentNode.contains(childNode3)).toBeFalsy();
|
||||
childNode2.firstChild = childNode3;
|
||||
childNode2.lastChild = childNode3;
|
||||
childNode3.parentNode = childNode2;
|
||||
expect(parentNode.contains(childNode3)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not implement appendChild', () => {
|
||||
@@ -143,6 +192,7 @@ describe('Basic RRDocument implementation', () => {
|
||||
expect(node.TEXT_NODE).toBe(document.TEXT_NODE);
|
||||
expect(node.firstChild).toBeNull();
|
||||
expect(node.lastChild).toBeNull();
|
||||
expect(node.previousSibling).toBeNull();
|
||||
expect(node.nextSibling).toBeNull();
|
||||
expect(node.contains).toBeDefined();
|
||||
expect(node.appendChild).toBeDefined();
|
||||
@@ -282,7 +332,7 @@ describe('Basic RRDocument implementation', () => {
|
||||
expect(() =>
|
||||
node.removeChild(node.createElement('div')),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Failed to execute 'removeChild' on 'RRDocument': The RRNode to be removed is not a child of this RRNode."`,
|
||||
`"Failed to execute 'removeChild' on 'RRNode': The RRNode to be removed is not a child of this RRNode."`,
|
||||
);
|
||||
expect(node.removeChild(documentType)).toBe(documentType);
|
||||
expect(documentType.parentNode).toBeNull();
|
||||
@@ -369,6 +419,7 @@ describe('Basic RRDocument implementation', () => {
|
||||
expect(node.TEXT_NODE).toBe(document.TEXT_NODE);
|
||||
expect(node.firstChild).toBeNull();
|
||||
expect(node.lastChild).toBeNull();
|
||||
expect(node.previousSibling).toBeNull();
|
||||
expect(node.nextSibling).toBeNull();
|
||||
expect(node.contains).toBeDefined();
|
||||
expect(node.appendChild).toBeDefined();
|
||||
@@ -404,6 +455,7 @@ describe('Basic RRDocument implementation', () => {
|
||||
expect(node.TEXT_NODE).toBe(document.TEXT_NODE);
|
||||
expect(node.firstChild).toBeNull();
|
||||
expect(node.lastChild).toBeNull();
|
||||
expect(node.previousSibling).toBeNull();
|
||||
expect(node.nextSibling).toBeNull();
|
||||
expect(node.contains).toBeDefined();
|
||||
expect(node.appendChild).toBeDefined();
|
||||
@@ -686,23 +738,39 @@ describe('Basic RRDocument implementation', () => {
|
||||
const node = document.createElement('div');
|
||||
expect(node.childNodes.length).toBe(0);
|
||||
|
||||
const child1 = document.createComment('span');
|
||||
const child1 = document.createElement('span');
|
||||
expect(node.appendChild(child1)).toBe(child1);
|
||||
expect(node.childNodes[0]).toEqual(child1);
|
||||
expect(node.childNodes[0]).toBe(child1);
|
||||
expect(node.firstChild).toBe(child1);
|
||||
expect(node.lastChild).toBe(child1);
|
||||
expect(child1.previousSibling).toBeNull();
|
||||
expect(child1.nextSibling).toBeNull();
|
||||
expect(child1.parentElement).toBe(node);
|
||||
expect(child1.parentNode).toBe(node);
|
||||
expect(child1.ownerDocument).toBe(document);
|
||||
expect(node.contains(child1)).toBeTruthy();
|
||||
|
||||
const child2 = document.createElement('p');
|
||||
expect(node.appendChild(child2)).toBe(child2);
|
||||
expect(node.childNodes[1]).toEqual(child2);
|
||||
expect(node.childNodes[1]).toBe(child2);
|
||||
expect(node.firstChild).toBe(child1);
|
||||
expect(node.lastChild).toBe(child2);
|
||||
expect(child1.previousSibling).toBeNull();
|
||||
expect(child1.nextSibling).toBe(child2);
|
||||
expect(child2.previousSibling).toBe(child1);
|
||||
expect(child2.nextSibling).toBeNull();
|
||||
expect(child2.parentElement).toBe(node);
|
||||
expect(child2.parentNode).toBe(node);
|
||||
expect(child2.ownerDocument).toBe(document);
|
||||
expect(node.contains(child1)).toBeTruthy();
|
||||
expect(node.contains(child2)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('can insert new child before an existing child', () => {
|
||||
const node = document.createElement('div');
|
||||
const child1 = document.createElement('h1');
|
||||
const child2 = document.createElement('h2');
|
||||
const child3 = document.createElement('h3');
|
||||
expect(() =>
|
||||
node.insertBefore(node, child1),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
@@ -710,42 +778,119 @@ describe('Basic RRDocument implementation', () => {
|
||||
);
|
||||
expect(node.insertBefore(child1, null)).toBe(child1);
|
||||
expect(node.childNodes[0]).toBe(child1);
|
||||
expect(node.childNodes.length).toBe(1);
|
||||
expect(node.firstChild).toBe(child1);
|
||||
expect(node.lastChild).toBe(child1);
|
||||
expect(child1.previousSibling).toBeNull();
|
||||
expect(child1.nextSibling).toBeNull();
|
||||
expect(child1.parentNode).toBe(node);
|
||||
expect(child1.parentElement).toBe(node);
|
||||
expect(child1.ownerDocument).toBe(document);
|
||||
expect(node.contains(child1)).toBeTruthy();
|
||||
|
||||
expect(node.insertBefore(child2, child1)).toBe(child2);
|
||||
expect(node.childNodes.length).toBe(2);
|
||||
expect(node.childNodes[0]).toBe(child2);
|
||||
expect(node.childNodes[1]).toBe(child1);
|
||||
expect(node.childNodes).toEqual([child2, child1]);
|
||||
expect(node.firstChild).toBe(child2);
|
||||
expect(node.lastChild).toBe(child1);
|
||||
expect(child1.previousSibling).toBe(child2);
|
||||
expect(child1.nextSibling).toBeNull();
|
||||
expect(child2.previousSibling).toBeNull();
|
||||
expect(child2.nextSibling).toBe(child1);
|
||||
expect(child2.parentNode).toBe(node);
|
||||
expect(child2.parentElement).toBe(node);
|
||||
expect(child2.ownerDocument).toBe(document);
|
||||
expect(node.contains(child2)).toBeTruthy();
|
||||
expect(node.contains(child1)).toBeTruthy();
|
||||
|
||||
expect(node.insertBefore(child3, child1)).toBe(child3);
|
||||
expect(node.childNodes).toEqual([child2, child3, child1]);
|
||||
expect(node.firstChild).toBe(child2);
|
||||
expect(node.lastChild).toBe(child1);
|
||||
expect(child1.previousSibling).toBe(child3);
|
||||
expect(child1.nextSibling).toBeNull();
|
||||
expect(child3.previousSibling).toBe(child2);
|
||||
expect(child3.nextSibling).toBe(child1);
|
||||
expect(child2.previousSibling).toBeNull();
|
||||
expect(child2.nextSibling).toBe(child3);
|
||||
expect(child3.parentNode).toBe(node);
|
||||
expect(child3.parentElement).toBe(node);
|
||||
expect(child3.ownerDocument).toBe(document);
|
||||
expect(node.contains(child2)).toBeTruthy();
|
||||
expect(node.contains(child3)).toBeTruthy();
|
||||
expect(node.contains(child1)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('can remove an existing child', () => {
|
||||
const node = document.createElement('div');
|
||||
const child1 = document.createElement('h1');
|
||||
const child2 = document.createElement('h2');
|
||||
const child3 = document.createElement('h3');
|
||||
node.appendChild(child1);
|
||||
node.appendChild(child2);
|
||||
expect(node.childNodes.length).toBe(2);
|
||||
expect(child1.parentNode).toBe(node);
|
||||
expect(child2.parentNode).toBe(node);
|
||||
expect(child1.parentElement).toBe(node);
|
||||
expect(child2.parentElement).toBe(node);
|
||||
node.appendChild(child3);
|
||||
expect(node.childNodes).toEqual([child1, child2, child3]);
|
||||
|
||||
expect(() =>
|
||||
node.removeChild(document.createElement('div')),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Failed to execute 'removeChild' on 'RRElement': The RRNode to be removed is not a child of this RRNode."`,
|
||||
`"Failed to execute 'removeChild' on 'RRNode': The RRNode to be removed is not a child of this RRNode."`,
|
||||
);
|
||||
expect(node.removeChild(child1)).toBe(child1);
|
||||
expect(child1.parentNode).toBeNull();
|
||||
expect(child1.parentElement).toBeNull();
|
||||
expect(node.childNodes.length).toBe(1);
|
||||
// Remove the middle child.
|
||||
expect(node.removeChild(child2)).toBe(child2);
|
||||
expect(node.childNodes.length).toBe(0);
|
||||
expect(node.childNodes).toEqual([child1, child3]);
|
||||
expect(node.contains(child2)).toBeFalsy();
|
||||
expect(node.firstChild).toBe(child1);
|
||||
expect(node.lastChild).toBe(child3);
|
||||
expect(child1.previousSibling).toBeNull();
|
||||
expect(child1.nextSibling).toBe(child3);
|
||||
expect(child3.previousSibling).toBe(child1);
|
||||
expect(child3.nextSibling).toBeNull();
|
||||
expect(child2.previousSibling).toBeNull();
|
||||
expect(child2.nextSibling).toBeNull();
|
||||
expect(child2.parentNode).toBeNull();
|
||||
expect(child2.parentElement).toBeNull();
|
||||
|
||||
// Remove the previous child.
|
||||
expect(node.removeChild(child1)).toBe(child1);
|
||||
expect(node.childNodes).toEqual([child3]);
|
||||
expect(node.contains(child1)).toBeFalsy();
|
||||
expect(node.firstChild).toBe(child3);
|
||||
expect(node.lastChild).toBe(child3);
|
||||
expect(child3.previousSibling).toBeNull();
|
||||
expect(child3.nextSibling).toBeNull();
|
||||
expect(child1.previousSibling).toBeNull();
|
||||
expect(child1.nextSibling).toBeNull();
|
||||
expect(child1.parentNode).toBeNull();
|
||||
expect(child1.parentElement).toBeNull();
|
||||
|
||||
node.insertBefore(child1, child3);
|
||||
expect(node.childNodes).toEqual([child1, child3]);
|
||||
// Remove the next child.
|
||||
expect(node.removeChild(child3)).toBe(child3);
|
||||
expect(node.childNodes).toEqual([child1]);
|
||||
expect(node.contains(child3)).toBeFalsy();
|
||||
expect(node.contains(child1)).toBeTruthy();
|
||||
expect(node.firstChild).toBe(child1);
|
||||
expect(node.lastChild).toBe(child1);
|
||||
expect(child1.previousSibling).toBeNull();
|
||||
expect(child1.nextSibling).toBeNull();
|
||||
expect(child3.previousSibling).toBeNull();
|
||||
expect(child3.nextSibling).toBeNull();
|
||||
expect(child3.parentNode).toBeNull();
|
||||
expect(child3.parentElement).toBeNull();
|
||||
|
||||
// Remove all children.
|
||||
expect(node.removeChild(child1)).toBe(child1);
|
||||
expect(node.childNodes).toEqual([]);
|
||||
expect(node.contains(child1)).toBeFalsy();
|
||||
expect(node.contains(child2)).toBeFalsy();
|
||||
expect(node.contains(child3)).toBeFalsy();
|
||||
expect(node.firstChild).toBeNull();
|
||||
expect(node.lastChild).toBeNull();
|
||||
expect(child1.previousSibling).toBeNull();
|
||||
expect(child1.nextSibling).toBeNull();
|
||||
expect(child1.parentNode).toBeNull();
|
||||
expect(child1.parentElement).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -768,6 +913,7 @@ describe('Basic RRDocument implementation', () => {
|
||||
expect(node.TEXT_NODE).toBe(document.TEXT_NODE);
|
||||
expect(node.firstChild).toBeNull();
|
||||
expect(node.lastChild).toBeNull();
|
||||
expect(node.previousSibling).toBeNull();
|
||||
expect(node.nextSibling).toBeNull();
|
||||
expect(node.contains).toBeDefined();
|
||||
expect(node.appendChild).toBeDefined();
|
||||
@@ -803,6 +949,7 @@ describe('Basic RRDocument implementation', () => {
|
||||
expect(node.TEXT_NODE).toBe(document.TEXT_NODE);
|
||||
expect(node.firstChild).toBeNull();
|
||||
expect(node.lastChild).toBeNull();
|
||||
expect(node.previousSibling).toBeNull();
|
||||
expect(node.nextSibling).toBeNull();
|
||||
expect(node.contains).toBeDefined();
|
||||
expect(node.appendChild).toBeDefined();
|
||||
@@ -838,6 +985,7 @@ describe('Basic RRDocument implementation', () => {
|
||||
expect(node.TEXT_NODE).toBe(document.TEXT_NODE);
|
||||
expect(node.firstChild).toBeNull();
|
||||
expect(node.lastChild).toBeNull();
|
||||
expect(node.previousSibling).toBeNull();
|
||||
expect(node.nextSibling).toBeNull();
|
||||
expect(node.contains).toBeDefined();
|
||||
expect(node.appendChild).toBeDefined();
|
||||
@@ -870,6 +1018,7 @@ describe('Basic RRDocument implementation', () => {
|
||||
expect(node.ELEMENT_NODE).toBe(document.ELEMENT_NODE);
|
||||
expect(node.TEXT_NODE).toBe(document.TEXT_NODE);
|
||||
expect(node.firstChild).toBeNull();
|
||||
expect(node.previousSibling).toBeNull();
|
||||
expect(node.nextSibling).toBeNull();
|
||||
expect(node.contains).toBeDefined();
|
||||
expect(node.appendChild).toBeDefined();
|
||||
|
||||
Reference in New Issue
Block a user