fix(tracker): child nodes unregistering during the commit phase: no 'Node not found' error in tracker

This commit is contained in:
Alex Kaminskii 2022-08-17 14:54:11 +02:00
parent 7e5e1ec8a8
commit 55cb3b9c45

View file

@ -43,17 +43,10 @@ function isObservable(node: Node): boolean {
- use document as a 0-node in the upper context (should be updated in player at first)
*/
/*
Nikita:
- rn we only send unbind event for parent (all child nodes will be cut in the live replay anyways)
to prevent sending 1k+ unbinds for child nodes and making replay file bigger than it should be
*/
enum RecentsType {
New,
Removed,
Changed,
RemovedChild,
}
export default abstract class Observer {
@ -76,7 +69,10 @@ export default abstract class Observer {
}
if (type === 'childList') {
for (let i = 0; i < mutation.removedNodes.length; i++) {
this.bindTree(mutation.removedNodes[i], true)
// Should be the same as bindTree(mutation.removedNodes[i]), but logic needs to be be untied
if (isObservable(mutation.removedNodes[i])) {
this.bindNode(mutation.removedNodes[i])
}
}
for (let i = 0; i < mutation.addedNodes.length; i++) {
this.bindTree(mutation.addedNodes[i])
@ -183,16 +179,11 @@ export default abstract class Observer {
if (isNew) {
this.recents.set(id, RecentsType.New)
} else if (this.recents.get(id) !== RecentsType.New) {
// can we do just `else` here?
this.recents.set(id, RecentsType.Removed)
}
}
private unbindChildNode(node: Node): void {
const [id] = this.app.nodes.registerNode(node)
this.recents.set(id, RecentsType.RemovedChild)
}
private bindTree(node: Node, isChildUnbinding = false): void {
private bindTree(node: Node): void {
if (!isObservable(node)) {
return
}
@ -202,7 +193,7 @@ export default abstract class Observer {
NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT,
{
acceptNode: (node) =>
isIgnored(node) || (this.app.nodes.getID(node) !== undefined && !isChildUnbinding)
isIgnored(node) || this.app.nodes.getID(node) !== undefined
? NodeFilter.FILTER_REJECT
: NodeFilter.FILTER_ACCEPT,
},
@ -210,18 +201,33 @@ export default abstract class Observer {
false,
)
while (walker.nextNode()) {
if (isChildUnbinding) {
this.unbindChildNode(walker.currentNode)
} else {
this.bindNode(walker.currentNode)
}
this.bindNode(walker.currentNode)
}
}
private unbindNode(node: Node) {
private unbindTree(node: Node) {
const id = this.app.nodes.unregisterNode(node)
if (id !== undefined && this.recents.get(id) === RecentsType.Removed) {
// Sending RemoveNode only for parent to maintain
this.app.send(RemoveNode(id))
// Unregistering all the children in order to clear the memory
const walker = document.createTreeWalker(
node,
NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT,
{
acceptNode: (node) =>
isIgnored(node) || this.app.nodes.getID(node) === undefined
? NodeFilter.FILTER_REJECT
: NodeFilter.FILTER_ACCEPT,
},
// @ts-ignore
false,
)
while (walker.nextNode()) {
this.app.nodes.unregisterNode(walker.currentNode)
}
// MBTODO: count and send RemovedNodesCount (for the page crash detection in heuristics)
}
}
@ -239,17 +245,17 @@ export default abstract class Observer {
if (!hasTag(node, 'HTML') || !this.isTopContext) {
if (parent === null) {
// Sometimes one observation contains attribute mutations for the removimg node, which gets ignored here.
// That shouldn't affect the visual rendering ( should it? )
this.unbindNode(node)
// That shouldn't affect the visual rendering ( should it? maybe when transition applied? )
this.unbindTree(node)
return false
}
parentID = this.app.nodes.getID(parent)
if (parentID === undefined) {
this.unbindNode(node)
this.unbindTree(node)
return false
}
if (!this.commitNode(parentID)) {
this.unbindNode(node)
this.unbindTree(node)
return false
}
this.app.sanitizer.handleNode(id, parentID, node)
@ -345,7 +351,8 @@ export default abstract class Observer {
this.clear()
}
// ISSSUE
// ISSSUE (nodeToBinde should be the same as node. Look at the comment about 0-node at the beginning of the file.)
// TODO: use one observer instance for all iframes/shadowRoots (composition instiad of inheritance)
protected observeRoot(
node: Node,
beforeCommit: (id?: number) => unknown,