Skip to content

Commit 88f348a

Browse files
committed
snapshot and rebuild shadow DOM
rrweb-io/rrweb#38
1 parent cf5c345 commit 88f348a

File tree

8 files changed

+717
-16
lines changed

8 files changed

+717
-16
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ build
55
dist
66
es
77
lib
8+
temp

src/rebuild.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
idNodeMap,
88
INode,
99
} from './types';
10+
import { isElement } from './utils';
1011

1112
const tagMap: tagMap = {
1213
script: 'noscript',
@@ -177,6 +178,25 @@ function buildNode(
177178
}
178179
}
179180
}
181+
if (n.isShadowHost) {
182+
/**
183+
* Since node is newly rebuilt, it should be a normal element
184+
* without shadowRoot.
185+
* But if there are some weird situations that has defined
186+
* custom element in the scope before we rebuild node, it may
187+
* register the shadowRoot earlier.
188+
* The logic in the 'else' block is just a try-my-best solution
189+
* for the corner case, please let we know if it is wrong and
190+
* we can remove it.
191+
*/
192+
if (!node.shadowRoot) {
193+
node.attachShadow({ mode: 'open' });
194+
} else {
195+
while (node.shadowRoot.firstChild) {
196+
node.shadowRoot.removeChild(node.shadowRoot.firstChild);
197+
}
198+
}
199+
}
180200
return node;
181201
case NodeType.Text:
182202
return doc.createTextNode(
@@ -240,7 +260,11 @@ export function buildNodeWithSN(
240260
continue;
241261
}
242262

243-
node.appendChild(childNode);
263+
if (childN.isShadow && isElement(node) && node.shadowRoot) {
264+
node.shadowRoot.appendChild(childNode);
265+
} else {
266+
node.appendChild(childNode);
267+
}
244268
if (afterAppend) {
245269
afterAppend(childNode);
246270
}

src/snapshot.ts

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
MaskInputOptions,
99
SlimDOMOptions,
1010
} from './types';
11+
import { isElement } from './utils';
1112

1213
let _id = 1;
1314
const tagNameRegex = RegExp('[^a-z0-9-_]');
@@ -622,26 +623,38 @@ export function serializeNodeWithId(
622623
) {
623624
preserveWhiteSpace = false;
624625
}
626+
const bypassOptions = {
627+
doc,
628+
map,
629+
blockClass,
630+
blockSelector,
631+
skipChild,
632+
inlineStylesheet,
633+
maskInputOptions,
634+
slimDOMOptions,
635+
recordCanvas,
636+
preserveWhiteSpace,
637+
onSerialize,
638+
onIframeLoad,
639+
iframeLoadTimeout,
640+
};
625641
for (const childN of Array.from(n.childNodes)) {
626-
const serializedChildNode = serializeNodeWithId(childN, {
627-
doc,
628-
map,
629-
blockClass,
630-
blockSelector,
631-
skipChild,
632-
inlineStylesheet,
633-
maskInputOptions,
634-
slimDOMOptions,
635-
recordCanvas,
636-
preserveWhiteSpace,
637-
onSerialize,
638-
onIframeLoad,
639-
iframeLoadTimeout,
640-
});
642+
const serializedChildNode = serializeNodeWithId(childN, bypassOptions);
641643
if (serializedChildNode) {
642644
serializedNode.childNodes.push(serializedChildNode);
643645
}
644646
}
647+
648+
if (isElement(n) && n.shadowRoot) {
649+
serializedNode.isShadowHost = true;
650+
for (const childN of Array.from(n.shadowRoot.childNodes)) {
651+
const serializedChildNode = serializeNodeWithId(childN, bypassOptions);
652+
if (serializedChildNode) {
653+
serializedChildNode.isShadow = true;
654+
serializedNode.childNodes.push(serializedChildNode);
655+
}
656+
}
657+
}
645658
}
646659

647660
if (

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ export type serializedNode = (
5656
| commentNode
5757
) & {
5858
rootId?: number;
59+
isShadowHost?: boolean;
60+
isShadow?: boolean;
5961
};
6062

6163
export type serializedNodeWithId = serializedNode & { id: number };

src/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { INode } from './types';
2+
3+
export function isElement(n: Node | INode): n is Element {
4+
return n.nodeType === n.ELEMENT_NODE;
5+
}

0 commit comments

Comments
 (0)