mutation observer v2 (#206)
There are some long-term issues in rrweb's mutation observer. A scenario cause problem: A list of DOM node: n1, n2, n3, n4, n5 Steps of modifying the nodes: 1. remove n1, n2, n3, n4 sequentially 2. append n4, n3, n2, n1 after n5 sequentially Then we got the added node data like this: (id: n4, prev: null, next: n3 ) (id: n3, prev: n4, next: n2 ) (id: n2, prev: n3, next: n1 ) (id: n1, prev: n2, next: null) The problem comes when we try to replay the first add node datum. Since its prev node is null, we rely on its next sibling n3. But n3 was not present at this moment, and in previous code, we fallback to append n4 to the last of its parent node. The solution is to defer the append of elements that missing siblings. But it is also hard to tell which node is the first one that needs to be appended. Take a step back and rethink the design of the mutation observer, we've found there are two implementations make things complicated. 1. We set the id to -1 when we seeing some nodes are not serialized yet. 2. We record both previous sibling and next sibling to determine the position of the node. But we can do better! First, we can put nodes with un-serialized siblings to a queue, and try to add it again later. Then we can just record the next sibling as 'the single truth' so we can be sure which node is the last one of its parent. This patch has implemented the new observer strategy. Data recorded with the new observer should no longer have any node with id -1. But for compatibility consideration, we still keep some replayer code that helps solve legacy data.
This commit is contained in:
@@ -191,17 +191,14 @@ function initMutationObserver(
|
||||
const addQueue: Node[] = [];
|
||||
const pushAdd = (n: Node) => {
|
||||
const parentId = mirror.getId((n.parentNode as Node) as INode);
|
||||
if (parentId === -1) {
|
||||
const nextId =
|
||||
n.nextSibling && mirror.getId((n.nextSibling as unknown) as INode);
|
||||
if (parentId === -1 || nextId === -1) {
|
||||
return addQueue.push(n);
|
||||
}
|
||||
adds.push({
|
||||
parentId,
|
||||
previousId: !n.previousSibling
|
||||
? n.previousSibling
|
||||
: mirror.getId((n.previousSibling as unknown) as INode),
|
||||
nextId: !n.nextSibling
|
||||
? n.nextSibling
|
||||
: mirror.getId((n.nextSibling as unknown) as INode),
|
||||
nextId,
|
||||
node: serializeNodeWithId(
|
||||
n,
|
||||
document,
|
||||
|
||||
@@ -69,7 +69,7 @@ export class Replayer {
|
||||
private nextUserInteractionEvent: eventWithTime | null;
|
||||
private noramlSpeed: number = -1;
|
||||
|
||||
private missingNodeRetryMap: missingNodeMap = {};
|
||||
private legacy_missingNodeRetryMap: missingNodeMap = {};
|
||||
|
||||
private service!: ReturnType<typeof createPlayerService>;
|
||||
|
||||
@@ -320,13 +320,13 @@ export class Replayer {
|
||||
private rebuildFullSnapshot(
|
||||
event: fullSnapshotEvent & { timestamp: number },
|
||||
) {
|
||||
if (Object.keys(this.missingNodeRetryMap).length) {
|
||||
if (Object.keys(this.legacy_missingNodeRetryMap).length) {
|
||||
console.warn(
|
||||
'Found unresolved missing node map',
|
||||
this.missingNodeRetryMap,
|
||||
this.legacy_missingNodeRetryMap,
|
||||
);
|
||||
}
|
||||
this.missingNodeRetryMap = {};
|
||||
this.legacy_missingNodeRetryMap = {};
|
||||
mirror.map = rebuild(event.data.node, this.iframe.contentDocument!)[1];
|
||||
const styleEl = document.createElement('style');
|
||||
const { documentElement, head } = this.iframe.contentDocument!;
|
||||
@@ -405,7 +405,9 @@ export class Replayer {
|
||||
}
|
||||
});
|
||||
|
||||
const missingNodeMap: missingNodeMap = { ...this.missingNodeRetryMap };
|
||||
const legacy_missingNodeMap: missingNodeMap = {
|
||||
...this.legacy_missingNodeRetryMap,
|
||||
};
|
||||
const queue: addedNodeMutation[] = [];
|
||||
|
||||
const appendNode = (mutation: addedNodeMutation) => {
|
||||
@@ -413,12 +415,7 @@ export class Replayer {
|
||||
if (!parent) {
|
||||
return queue.push(mutation);
|
||||
}
|
||||
const target = buildNodeWithSN(
|
||||
mutation.node,
|
||||
this.iframe.contentDocument!,
|
||||
mirror.map,
|
||||
true,
|
||||
) as Node;
|
||||
|
||||
let previous: Node | null = null;
|
||||
let next: Node | null = null;
|
||||
if (mutation.previousId) {
|
||||
@@ -427,9 +424,21 @@ export class Replayer {
|
||||
if (mutation.nextId) {
|
||||
next = mirror.getNode(mutation.nextId) as Node;
|
||||
}
|
||||
// next not present at this moment
|
||||
if (mutation.nextId !== null && mutation.nextId !== -1 && !next) {
|
||||
return queue.push(mutation);
|
||||
}
|
||||
|
||||
const target = buildNodeWithSN(
|
||||
mutation.node,
|
||||
this.iframe.contentDocument!,
|
||||
mirror.map,
|
||||
true,
|
||||
) as Node;
|
||||
|
||||
// legacy data, we should not have -1 siblings any more
|
||||
if (mutation.previousId === -1 || mutation.nextId === -1) {
|
||||
missingNodeMap[mutation.node.id] = {
|
||||
legacy_missingNodeMap[mutation.node.id] = {
|
||||
node: target,
|
||||
mutation,
|
||||
};
|
||||
@@ -453,7 +462,12 @@ export class Replayer {
|
||||
}
|
||||
|
||||
if (mutation.previousId || mutation.nextId) {
|
||||
this.resolveMissingNode(missingNodeMap, parent, target, mutation);
|
||||
this.legacy_resolveMissingNode(
|
||||
legacy_missingNodeMap,
|
||||
parent,
|
||||
target,
|
||||
mutation,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -469,8 +483,8 @@ export class Replayer {
|
||||
appendNode(mutation);
|
||||
}
|
||||
|
||||
if (Object.keys(missingNodeMap).length) {
|
||||
Object.assign(this.missingNodeRetryMap, missingNodeMap);
|
||||
if (Object.keys(legacy_missingNodeMap).length) {
|
||||
Object.assign(this.legacy_missingNodeRetryMap, legacy_missingNodeMap);
|
||||
}
|
||||
|
||||
d.texts.forEach((mutation) => {
|
||||
@@ -685,7 +699,7 @@ export class Replayer {
|
||||
}
|
||||
}
|
||||
|
||||
private resolveMissingNode(
|
||||
private legacy_resolveMissingNode(
|
||||
map: missingNodeMap,
|
||||
parent: Node,
|
||||
target: Node,
|
||||
@@ -698,18 +712,18 @@ export class Replayer {
|
||||
const { node, mutation } = previousInMap as missingNode;
|
||||
parent.insertBefore(node, target);
|
||||
delete map[mutation.node.id];
|
||||
delete this.missingNodeRetryMap[mutation.node.id];
|
||||
delete this.legacy_missingNodeRetryMap[mutation.node.id];
|
||||
if (mutation.previousId || mutation.nextId) {
|
||||
this.resolveMissingNode(map, parent, node as Node, mutation);
|
||||
this.legacy_resolveMissingNode(map, parent, node as Node, mutation);
|
||||
}
|
||||
}
|
||||
if (nextInMap) {
|
||||
const { node, mutation } = nextInMap as missingNode;
|
||||
parent.insertBefore(node, target.nextSibling);
|
||||
delete map[mutation.node.id];
|
||||
delete this.missingNodeRetryMap[mutation.node.id];
|
||||
delete this.legacy_missingNodeRetryMap[mutation.node.id];
|
||||
if (mutation.previousId || mutation.nextId) {
|
||||
this.resolveMissingNode(map, parent, node as Node, mutation);
|
||||
this.legacy_resolveMissingNode(map, parent, node as Node, mutation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +195,8 @@ export type removedNodeMutation = {
|
||||
|
||||
export type addedNodeMutation = {
|
||||
parentId: number;
|
||||
previousId: number | null;
|
||||
// Newly recorded mutations will not have previousId any more, just for compatibility
|
||||
previousId?: number | null;
|
||||
nextId: number | null;
|
||||
node: serializedNodeWithId;
|
||||
};
|
||||
|
||||
@@ -479,7 +479,6 @@ exports[`character-data 1`] = `
|
||||
\\"adds\\": [
|
||||
{
|
||||
\\"parentId\\": 6,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -635,7 +634,6 @@ exports[`child-list 1`] = `
|
||||
\\"adds\\": [
|
||||
{
|
||||
\\"parentId\\": 6,
|
||||
\\"previousId\\": 7,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -2480,7 +2478,6 @@ exports[`move-node-1 1`] = `
|
||||
\\"adds\\": [
|
||||
{
|
||||
\\"parentId\\": 6,
|
||||
\\"previousId\\": 9,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -2492,7 +2489,6 @@ exports[`move-node-1 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 11,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": 13,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -2502,7 +2498,6 @@ exports[`move-node-1 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 11,
|
||||
\\"previousId\\": 12,
|
||||
\\"nextId\\": 18,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -2514,7 +2509,6 @@ exports[`move-node-1 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 13,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": 15,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -2524,7 +2518,6 @@ exports[`move-node-1 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 13,
|
||||
\\"previousId\\": 14,
|
||||
\\"nextId\\": 17,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -2536,7 +2529,6 @@ exports[`move-node-1 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 15,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -2546,7 +2538,6 @@ exports[`move-node-1 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 13,
|
||||
\\"previousId\\": 15,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -2556,7 +2547,6 @@ exports[`move-node-1 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 11,
|
||||
\\"previousId\\": 13,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -2748,7 +2738,6 @@ exports[`move-node-2 1`] = `
|
||||
\\"adds\\": [
|
||||
{
|
||||
\\"parentId\\": 11,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": 13,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -2758,7 +2747,6 @@ exports[`move-node-2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 11,
|
||||
\\"previousId\\": 12,
|
||||
\\"nextId\\": 18,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -2770,7 +2758,6 @@ exports[`move-node-2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 13,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": 15,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -2780,7 +2767,6 @@ exports[`move-node-2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 13,
|
||||
\\"previousId\\": 14,
|
||||
\\"nextId\\": 17,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -2792,7 +2778,6 @@ exports[`move-node-2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 15,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -2802,7 +2787,6 @@ exports[`move-node-2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 13,
|
||||
\\"previousId\\": 15,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -2812,7 +2796,6 @@ exports[`move-node-2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 11,
|
||||
\\"previousId\\": 13,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -2822,7 +2805,6 @@ exports[`move-node-2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 4,
|
||||
\\"previousId\\": 22,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -2834,7 +2816,6 @@ exports[`move-node-2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 23,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -3804,7 +3785,7 @@ exports[`select2 1`] = `
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"id\\": 75,
|
||||
\\"id\\": 70,
|
||||
\\"attributes\\": {
|
||||
\\"style\\": \\"\\"
|
||||
}
|
||||
@@ -3823,7 +3804,7 @@ exports[`select2 1`] = `
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"id\\": 67,
|
||||
\\"id\\": 72,
|
||||
\\"attributes\\": {
|
||||
\\"class\\": \\"select2-results-dept-0 select2-result select2-result-selectable select2-highlighted\\"
|
||||
}
|
||||
@@ -3842,7 +3823,6 @@ exports[`select2 1`] = `
|
||||
\\"adds\\": [
|
||||
{
|
||||
\\"parentId\\": 25,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": 34,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -3858,7 +3838,6 @@ exports[`select2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 26,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": 28,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -3868,7 +3847,6 @@ exports[`select2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 26,
|
||||
\\"previousId\\": 27,
|
||||
\\"nextId\\": 30,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -3883,7 +3861,6 @@ exports[`select2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 28,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -3893,7 +3870,6 @@ exports[`select2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 26,
|
||||
\\"previousId\\": 28,
|
||||
\\"nextId\\": 31,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -3907,7 +3883,6 @@ exports[`select2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 26,
|
||||
\\"previousId\\": 30,
|
||||
\\"nextId\\": 32,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -3917,7 +3892,6 @@ exports[`select2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 26,
|
||||
\\"previousId\\": 31,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -3932,7 +3906,6 @@ exports[`select2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 32,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -3946,7 +3919,6 @@ exports[`select2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 18,
|
||||
\\"previousId\\": -1,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -3961,7 +3933,6 @@ exports[`select2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 36,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": 38,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -3971,7 +3942,6 @@ exports[`select2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 36,
|
||||
\\"previousId\\": 37,
|
||||
\\"nextId\\": 44,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -3985,7 +3955,6 @@ exports[`select2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 38,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": 40,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -3995,7 +3964,6 @@ exports[`select2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 38,
|
||||
\\"previousId\\": 39,
|
||||
\\"nextId\\": 41,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -4010,7 +3978,6 @@ exports[`select2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 38,
|
||||
\\"previousId\\": 40,
|
||||
\\"nextId\\": 42,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -4020,7 +3987,6 @@ exports[`select2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 38,
|
||||
\\"previousId\\": 41,
|
||||
\\"nextId\\": 43,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -4046,7 +4012,6 @@ exports[`select2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 38,
|
||||
\\"previousId\\": 42,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -4056,7 +4021,6 @@ exports[`select2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 36,
|
||||
\\"previousId\\": 38,
|
||||
\\"nextId\\": 45,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -4066,7 +4030,6 @@ exports[`select2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 36,
|
||||
\\"previousId\\": 44,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -4082,62 +4045,6 @@ exports[`select2 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 45,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": -1,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"li\\",
|
||||
\\"attributes\\": {
|
||||
\\"class\\": \\"select2-results-dept-0 select2-result select2-result-selectable select2-highlighted\\",
|
||||
\\"role\\": \\"presentation\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 67
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 67,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"div\\",
|
||||
\\"attributes\\": {
|
||||
\\"class\\": \\"select2-result-label\\",
|
||||
\\"id\\": \\"select2-result-label-2\\",
|
||||
\\"role\\": \\"option\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 68
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 68,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": -1,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"span\\",
|
||||
\\"attributes\\": {
|
||||
\\"class\\": \\"select2-match\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 69
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 68,
|
||||
\\"previousId\\": 69,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"A\\",
|
||||
\\"id\\": 70
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 45,
|
||||
\\"previousId\\": 67,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -4147,12 +4054,11 @@ exports[`select2 1`] = `
|
||||
\\"role\\": \\"presentation\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 71
|
||||
\\"id\\": 67
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 71,
|
||||
\\"previousId\\": null,
|
||||
\\"parentId\\": 67,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -4163,36 +4069,20 @@ exports[`select2 1`] = `
|
||||
\\"role\\": \\"option\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 72
|
||||
\\"id\\": 68
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 72,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": -1,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"span\\",
|
||||
\\"attributes\\": {
|
||||
\\"class\\": \\"select2-match\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 73
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 72,
|
||||
\\"previousId\\": 73,
|
||||
\\"parentId\\": 68,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"B\\",
|
||||
\\"id\\": 74
|
||||
\\"id\\": 69
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 18,
|
||||
\\"previousId\\": 66,
|
||||
\\"nextId\\": 36,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -4203,16 +4093,79 @@ exports[`select2 1`] = `
|
||||
\\"style\\": \\"\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 75
|
||||
\\"id\\": 70
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 62,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"2 results are available, use up and down arrow keys to navigate.\\",
|
||||
\\"id\\": 71
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 45,
|
||||
\\"nextId\\": 67,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"li\\",
|
||||
\\"attributes\\": {
|
||||
\\"class\\": \\"select2-results-dept-0 select2-result select2-result-selectable select2-highlighted\\",
|
||||
\\"role\\": \\"presentation\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 72
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 72,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"div\\",
|
||||
\\"attributes\\": {
|
||||
\\"class\\": \\"select2-result-label\\",
|
||||
\\"id\\": \\"select2-result-label-2\\",
|
||||
\\"role\\": \\"option\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 73
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 73,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"A\\",
|
||||
\\"id\\": 74
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 68,
|
||||
\\"nextId\\": 69,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"span\\",
|
||||
\\"attributes\\": {
|
||||
\\"class\\": \\"select2-match\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 75
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 73,
|
||||
\\"nextId\\": 74,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"span\\",
|
||||
\\"attributes\\": {
|
||||
\\"class\\": \\"select2-match\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 76
|
||||
}
|
||||
}
|
||||
@@ -4224,7 +4177,7 @@ exports[`select2 1`] = `
|
||||
\\"data\\": {
|
||||
\\"source\\": 2,
|
||||
\\"type\\": 0,
|
||||
\\"id\\": 75
|
||||
\\"id\\": 70
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -4250,7 +4203,7 @@ exports[`select2 1`] = `
|
||||
\\"data\\": {
|
||||
\\"source\\": 2,
|
||||
\\"type\\": 1,
|
||||
\\"id\\": 75
|
||||
\\"id\\": 70
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -4276,7 +4229,7 @@ exports[`select2 1`] = `
|
||||
\\"texts\\": [],
|
||||
\\"attributes\\": [
|
||||
{
|
||||
\\"id\\": 75,
|
||||
\\"id\\": 70,
|
||||
\\"attributes\\": {
|
||||
\\"style\\": \\"display: none;\\"
|
||||
}
|
||||
@@ -4309,21 +4262,20 @@ exports[`select2 1`] = `
|
||||
\\"removes\\": [
|
||||
{
|
||||
\\"parentId\\": 18,
|
||||
\\"id\\": 75
|
||||
\\"id\\": 70
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 45,
|
||||
\\"id\\": 72
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 45,
|
||||
\\"id\\": 67
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 45,
|
||||
\\"id\\": 71
|
||||
}
|
||||
],
|
||||
\\"adds\\": [
|
||||
{
|
||||
\\"parentId\\": 18,
|
||||
\\"previousId\\": 66,
|
||||
\\"nextId\\": 36,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -4334,7 +4286,7 @@ exports[`select2 1`] = `
|
||||
\\"style\\": \\"display: none;\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 75
|
||||
\\"id\\": 70
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -4350,3 +4302,177 @@ exports[`select2 1`] = `
|
||||
}
|
||||
]"
|
||||
`;
|
||||
|
||||
exports[`serialize-before-record 1`] = `
|
||||
"[
|
||||
{
|
||||
\\"type\\": 0,
|
||||
\\"data\\": {}
|
||||
},
|
||||
{
|
||||
\\"type\\": 1,
|
||||
\\"data\\": {}
|
||||
},
|
||||
{
|
||||
\\"type\\": 4,
|
||||
\\"data\\": {
|
||||
\\"href\\": \\"about:blank\\",
|
||||
\\"width\\": 1920,
|
||||
\\"height\\": 1080
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"data\\": {
|
||||
\\"node\\": {
|
||||
\\"type\\": 0,
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"html\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"head\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 3
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"body\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 5
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"p\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"mutation observer\\",
|
||||
\\"id\\": 7
|
||||
}
|
||||
],
|
||||
\\"id\\": 6
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 8
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"ul\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 10
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"li\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 11
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 12
|
||||
}
|
||||
],
|
||||
\\"id\\": 9
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n\\\\n \\",
|
||||
\\"id\\": 13
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"script\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
|
||||
\\"id\\": 15
|
||||
}
|
||||
],
|
||||
\\"id\\": 14
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\\\n \\",
|
||||
\\"id\\": 16
|
||||
}
|
||||
],
|
||||
\\"id\\": 4
|
||||
}
|
||||
],
|
||||
\\"id\\": 2
|
||||
}
|
||||
],
|
||||
\\"id\\": 1
|
||||
},
|
||||
\\"initialOffset\\": {
|
||||
\\"left\\": 0,
|
||||
\\"top\\": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 0,
|
||||
\\"texts\\": [],
|
||||
\\"attributes\\": [],
|
||||
\\"removes\\": [],
|
||||
\\"adds\\": [
|
||||
{
|
||||
\\"parentId\\": 9,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"li\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 17
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 9,
|
||||
\\"nextId\\": 17,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"li\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 18
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 9,
|
||||
\\"nextId\\": 18,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"li\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 19
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]"
|
||||
`;
|
||||
|
||||
@@ -82,7 +82,6 @@ exports[`async-checkout 1`] = `
|
||||
\\"adds\\": [
|
||||
{
|
||||
\\"parentId\\": 4,
|
||||
\\"previousId\\": 7,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -94,7 +93,6 @@ exports[`async-checkout 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 8,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -117,7 +115,6 @@ exports[`async-checkout 1`] = `
|
||||
\\"adds\\": [
|
||||
{
|
||||
\\"parentId\\": 9,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -220,7 +217,6 @@ exports[`async-checkout 1`] = `
|
||||
\\"adds\\": [
|
||||
{
|
||||
\\"parentId\\": 4,
|
||||
\\"previousId\\": 8,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
@@ -232,7 +228,6 @@ exports[`async-checkout 1`] = `
|
||||
},
|
||||
{
|
||||
\\"parentId\\": 9,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 3,
|
||||
@@ -409,7 +404,6 @@ exports[`stylesheet-rules 1`] = `
|
||||
\\"adds\\": [
|
||||
{
|
||||
\\"parentId\\": 3,
|
||||
\\"previousId\\": null,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
|
||||
12
test/html/shuffle.html
Normal file
12
test/html/shuffle.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>shuffle</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- prettier-ignore -->
|
||||
<ul><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>
|
||||
</body>
|
||||
</html>
|
||||
@@ -3,6 +3,7 @@ import * as path from 'path';
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { assertSnapshot, launchPuppeteer } from './utils';
|
||||
import { Suite } from 'mocha';
|
||||
import { expect } from 'chai';
|
||||
import { recordOptions, eventWithTime } from '../src/types';
|
||||
|
||||
interface ISuite extends Suite {
|
||||
@@ -63,7 +64,7 @@ describe('record integration tests', function (this: ISuite) {
|
||||
|
||||
const snapshots = await page.evaluate('window.snapshots');
|
||||
assertSnapshot(snapshots, __filename, 'form');
|
||||
}).timeout(5000);
|
||||
});
|
||||
|
||||
it('can record childList mutations', async () => {
|
||||
const page: puppeteer.Page = await this.browser.newPage();
|
||||
@@ -81,7 +82,7 @@ describe('record integration tests', function (this: ISuite) {
|
||||
|
||||
const snapshots = await page.evaluate('window.snapshots');
|
||||
assertSnapshot(snapshots, __filename, 'child-list');
|
||||
}).timeout(5000);
|
||||
});
|
||||
|
||||
it('can record character data muatations', async () => {
|
||||
const page: puppeteer.Page = await this.browser.newPage();
|
||||
@@ -219,4 +220,45 @@ describe('record integration tests', function (this: ISuite) {
|
||||
const snapshots = await page.evaluate('window.snapshots');
|
||||
assertSnapshot(snapshots, __filename, 'react-styled-components');
|
||||
});
|
||||
|
||||
it('will serialize node before record', async () => {
|
||||
const page: puppeteer.Page = await this.browser.newPage();
|
||||
await page.goto('about:blank');
|
||||
await page.setContent(getHtml.call(this, 'mutation-observer.html'));
|
||||
|
||||
await page.evaluate(() => {
|
||||
const ul = document.querySelector('ul') as HTMLUListElement;
|
||||
let count = 3;
|
||||
while (count > 0) {
|
||||
count--;
|
||||
const li = document.createElement('li');
|
||||
ul.appendChild(li);
|
||||
}
|
||||
});
|
||||
|
||||
const snapshots = await page.evaluate('window.snapshots');
|
||||
assertSnapshot(snapshots, __filename, 'serialize-before-record');
|
||||
});
|
||||
|
||||
it('will defer missing next node mutation', async () => {
|
||||
const page: puppeteer.Page = await this.browser.newPage();
|
||||
await page.goto('about:blank');
|
||||
await page.setContent(getHtml.call(this, 'shuffle.html'));
|
||||
|
||||
const text = await page.evaluate(() => {
|
||||
const els = Array.prototype.slice.call(document.querySelectorAll('li'));
|
||||
const parent = document.querySelector('ul')!;
|
||||
parent.removeChild(els[3]);
|
||||
parent.removeChild(els[2]);
|
||||
parent.removeChild(els[1]);
|
||||
parent.removeChild(els[0]);
|
||||
parent.insertBefore(els[3], els[4]);
|
||||
parent.insertBefore(els[2], els[4]);
|
||||
parent.insertBefore(els[1], els[4]);
|
||||
parent.insertBefore(els[0], els[4]);
|
||||
return parent.innerText;
|
||||
});
|
||||
|
||||
expect(text).to.equal('4\n3\n2\n1\n5');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user