impl shadow DOM manager

part of #38
1. observe DOM mutations in shadow DOM
2. rebuild DOM mutations in shadow DOM
This commit is contained in:
Yanzhen Yu
2026-04-01 12:00:00 +08:00
parent 66c7c8f028
commit 0e688bba0c
12 changed files with 680 additions and 48 deletions

View File

@@ -5,7 +5,7 @@ import {
MaskInputOptions,
SlimDOMOptions,
IGNORED_NODE,
NodeType,
isShadowRoot,
} from 'rrweb-snapshot';
import {
mutationRecord,
@@ -16,8 +16,16 @@ import {
removedNodeMutation,
addedNodeMutation,
} from '../types';
import { mirror, isBlocked, isAncestorRemoved, isIgnored } from '../utils';
import {
mirror,
isBlocked,
isAncestorRemoved,
isIgnored,
isIframeINode,
hasShadowRoot,
} from '../utils';
import { IframeManager } from './iframe-manager';
import { ShadowDomManager } from './shadow-dom-manager';
type DoubleLinkedListNode = {
previous: DoubleLinkedListNode | null;
@@ -158,6 +166,7 @@ export default class MutationBuffer {
private doc: Document;
private iframeManager: IframeManager;
private shadowDomManager: ShadowDomManager;
public init(
cb: mutationCallBack,
@@ -169,6 +178,7 @@ export default class MutationBuffer {
slimDOMOptions: SlimDOMOptions,
doc: Document,
iframeManager: IframeManager,
shadowDomManager: ShadowDomManager,
) {
this.blockClass = blockClass;
this.blockSelector = blockSelector;
@@ -179,6 +189,7 @@ export default class MutationBuffer {
this.emissionCallback = cb;
this.doc = doc;
this.iframeManager = iframeManager;
this.shadowDomManager = shadowDomManager;
}
public freeze() {
@@ -236,10 +247,14 @@ export default class MutationBuffer {
return nextId;
};
const pushAdd = (n: Node) => {
if (!n.parentNode || !this.doc.contains(n)) {
const shadowHost: Element | null = (n.getRootNode() as ShadowRoot)?.host;
const notInDoc = !this.doc.contains(n) && !this.doc.contains(shadowHost);
if (!n.parentNode || notInDoc) {
return;
}
const parentId = mirror.getId((n.parentNode as Node) as INode);
const parentId = isShadowRoot(n.parentNode)
? mirror.getId((shadowHost as unknown) as INode)
: mirror.getId((n.parentNode as Node) as INode);
const nextId = getNextId(n);
if (parentId === -1 || nextId === -1) {
return addList.addNode(n);
@@ -255,13 +270,11 @@ export default class MutationBuffer {
slimDOMOptions: this.slimDOMOptions,
recordCanvas: this.recordCanvas,
onSerialize: (currentN) => {
if (
currentN.__sn.type === NodeType.Element &&
currentN.__sn.tagName === 'iframe'
) {
this.iframeManager.addIframe(
(currentN as unknown) as HTMLIFrameElement,
);
if (isIframeINode(currentN)) {
this.iframeManager.addIframe(currentN);
}
if (hasShadowRoot(n)) {
this.shadowDomManager.addShadowRoot(n.shadowRoot, document);
}
},
onIframeLoad: (iframe, childSn) => {
@@ -418,6 +431,7 @@ export default class MutationBuffer {
// overwrite attribute if the mutations was triggered in same time
item.attributes[m.attributeName!] = transformAttribute(
this.doc,
(m.target as HTMLElement).tagName,
m.attributeName!,
value!,
);
@@ -427,7 +441,9 @@ export default class MutationBuffer {
m.addedNodes.forEach((n) => this.genAdds(n, m.target));
m.removedNodes.forEach((n) => {
const nodeId = mirror.getId(n as INode);
const parentId = mirror.getId(m.target as INode);
const parentId = isShadowRoot(m.target)
? mirror.getId((m.target.host as unknown) as INode)
: mirror.getId(m.target as INode);
if (
isBlocked(n, this.blockClass) ||
isBlocked(m.target, this.blockClass) ||
@@ -463,6 +479,7 @@ export default class MutationBuffer {
this.removes.push({
parentId,
id: nodeId,
isShadow: isShadowRoot(m.target) ? true : undefined,
});
}
this.mapRemoves.push(n);