snapshot and rebuild shadow DOM
https://github.com/rrweb-io/rrweb/issues/38
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
5
src/utils.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { INode } from './types';
|
||||
|
||||
export function isElement(n: Node | INode): n is Element {
|
||||
return n.nodeType === n.ELEMENT_NODE;
|
||||
}
|
||||
Reference in New Issue
Block a user