rrweb-snapshot icon indicating copy to clipboard operation
rrweb-snapshot copied to clipboard

snapshot is produced with non-unique IDs

Open hvpavel opened this issue 4 years ago • 2 comments

In some cases calling snapshot(document) produces a tree with elements which IDs are not unique. In most cases these elements are the root element document and the first element from <head> e.g. <link> or <style>.

It leads to the situation where in Player the root element document with id = 1 is overwritten by some child node, that also has id = 1. In this case IncrementalSource.Event, which is fired for document, won't be correctly processed by the Player instance.

Here the assignment of non-unique IDs happen: https://github.com/rrweb-io/rrweb-snapshot/blob/master/src/snapshot.ts#L693

hvpavel avatar May 12 '21 18:05 hvpavel

Is this happening when you snapshot a page more than once?

Yuyz0112 avatar Jun 10 '21 11:06 Yuyz0112

No, it isn't happening when requesting a page snapshot more than once.

I was able to identify that for some websites the assignment node.__sn = {/* serialized node */} gets transferred through the page reload. Probably it's happening because of particular cache configuration, but I didn't find the exact reason.

So if you create a snapshot for a page and then refresh it, you'll be able to see that, for instance, document has __sn property assigned even if you don't load rrweb-snapshot after the page refresh.

Unfortunately, I don't have any strong case to share. However, I've noticed that this issue is happening more often in Safari than in any other browser (probably only in Safari).

I was able to fix it by collecting the serialized objects into a WeakMap instead of directly modifying the Node element.

In a separate file:

export const serializedCache = new WeakMap<Node, serializedNodeWithId>();

Restoring cache:

let serializedNode = serializedCache.get(node);
// Try to reuse the previous id
if (serializedNode) {
  id = serializedNode.id;
}

Caching a serialized node:

const serializedNode = Object.assign(_serializedNode, { id });
serializedCache.set(n, serializedNode);

I can create a PR if you approve this modification.

hvpavel avatar Jun 17 '21 23:06 hvpavel