snapshot and rebuild shadow DOM

https://github.com/rrweb-io/rrweb/issues/38
This commit is contained in:
Yanzhen Yu
2021-03-13 20:46:18 +08:00
parent cf5c34592e
commit 88f348a57b
8 changed files with 717 additions and 16 deletions

View File

@@ -7,6 +7,7 @@ import {
idNodeMap,
INode,
} from './types';
import { isElement } from './utils';
const tagMap: tagMap = {
script: 'noscript',
@@ -177,6 +178,25 @@ function buildNode(
}
}
}
if (n.isShadowHost) {
/**
* Since node is newly rebuilt, it should be a normal element
* without shadowRoot.
* But if there are some weird situations that has defined
* custom element in the scope before we rebuild node, it may
* register the shadowRoot earlier.
* The logic in the 'else' block is just a try-my-best solution
* for the corner case, please let we know if it is wrong and
* we can remove it.
*/
if (!node.shadowRoot) {
node.attachShadow({ mode: 'open' });
} else {
while (node.shadowRoot.firstChild) {
node.shadowRoot.removeChild(node.shadowRoot.firstChild);
}
}
}
return node;
case NodeType.Text:
return doc.createTextNode(
@@ -240,7 +260,11 @@ export function buildNodeWithSN(
continue;
}
node.appendChild(childNode);
if (childN.isShadow && isElement(node) && node.shadowRoot) {
node.shadowRoot.appendChild(childNode);
} else {
node.appendChild(childNode);
}
if (afterAppend) {
afterAppend(childNode);
}

View File

@@ -8,6 +8,7 @@ import {
MaskInputOptions,
SlimDOMOptions,
} from './types';
import { isElement } from './utils';
let _id = 1;
const tagNameRegex = RegExp('[^a-z0-9-_]');
@@ -622,26 +623,38 @@ export function serializeNodeWithId(
) {
preserveWhiteSpace = false;
}
const bypassOptions = {
doc,
map,
blockClass,
blockSelector,
skipChild,
inlineStylesheet,
maskInputOptions,
slimDOMOptions,
recordCanvas,
preserveWhiteSpace,
onSerialize,
onIframeLoad,
iframeLoadTimeout,
};
for (const childN of Array.from(n.childNodes)) {
const serializedChildNode = serializeNodeWithId(childN, {
doc,
map,
blockClass,
blockSelector,
skipChild,
inlineStylesheet,
maskInputOptions,
slimDOMOptions,
recordCanvas,
preserveWhiteSpace,
onSerialize,
onIframeLoad,
iframeLoadTimeout,
});
const serializedChildNode = serializeNodeWithId(childN, bypassOptions);
if (serializedChildNode) {
serializedNode.childNodes.push(serializedChildNode);
}
}
if (isElement(n) && n.shadowRoot) {
serializedNode.isShadowHost = true;
for (const childN of Array.from(n.shadowRoot.childNodes)) {
const serializedChildNode = serializeNodeWithId(childN, bypassOptions);
if (serializedChildNode) {
serializedChildNode.isShadow = true;
serializedNode.childNodes.push(serializedChildNode);
}
}
}
}
if (

View File

@@ -56,6 +56,8 @@ export type serializedNode = (
| commentNode
) & {
rootId?: number;
isShadowHost?: boolean;
isShadow?: boolean;
};
export type serializedNodeWithId = serializedNode & { id: number };

5
src/utils.ts Normal file
View File

@@ -0,0 +1,5 @@
import { INode } from './types';
export function isElement(n: Node | INode): n is Element {
return n.nodeType === n.ELEMENT_NODE;
}