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:
@@ -7,6 +7,7 @@ import {
|
||||
getWindowHeight,
|
||||
polyfill,
|
||||
isIframeINode,
|
||||
hasShadowRoot,
|
||||
} from '../utils';
|
||||
import {
|
||||
EventType,
|
||||
@@ -16,8 +17,10 @@ import {
|
||||
IncrementalSource,
|
||||
listenerHandler,
|
||||
LogRecordOptions,
|
||||
mutationCallbackParam,
|
||||
} from '../types';
|
||||
import { IframeManager } from './iframe-manager';
|
||||
import { ShadowDomManager } from './shadow-dom-manager';
|
||||
|
||||
function wrapEvent(e: event): eventWithTime {
|
||||
return {
|
||||
@@ -179,17 +182,33 @@ function record<T = eventWithTime>(
|
||||
}
|
||||
};
|
||||
|
||||
const wrappedMutationEmit = (m: mutationCallbackParam) => {
|
||||
wrappedEmit(
|
||||
wrapEvent({
|
||||
type: EventType.IncrementalSnapshot,
|
||||
data: {
|
||||
source: IncrementalSource.Mutation,
|
||||
...m,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const iframeManager = new IframeManager({
|
||||
mutationCb: (m) =>
|
||||
wrappedEmit(
|
||||
wrapEvent({
|
||||
type: EventType.IncrementalSnapshot,
|
||||
data: {
|
||||
source: IncrementalSource.Mutation,
|
||||
...m,
|
||||
},
|
||||
}),
|
||||
),
|
||||
mutationCb: wrappedMutationEmit,
|
||||
});
|
||||
|
||||
const shadowDomManager = new ShadowDomManager({
|
||||
mutationCb: wrappedMutationEmit,
|
||||
bypassOptions: {
|
||||
blockClass,
|
||||
blockSelector,
|
||||
inlineStylesheet,
|
||||
maskInputOptions,
|
||||
recordCanvas,
|
||||
slimDOMOptions,
|
||||
iframeManager,
|
||||
},
|
||||
});
|
||||
|
||||
takeFullSnapshot = (isCheckout = false) => {
|
||||
@@ -217,6 +236,9 @@ function record<T = eventWithTime>(
|
||||
if (isIframeINode(n)) {
|
||||
iframeManager.addIframe(n);
|
||||
}
|
||||
if (hasShadowRoot(n)) {
|
||||
shadowDomManager.addShadowRoot(n.shadowRoot, document);
|
||||
}
|
||||
},
|
||||
onIframeLoad: (iframe, childSn) => {
|
||||
iframeManager.attachIframe(iframe, childSn);
|
||||
@@ -271,16 +293,7 @@ function record<T = eventWithTime>(
|
||||
const observe = (doc: Document) => {
|
||||
return initObservers(
|
||||
{
|
||||
mutationCb: (m) =>
|
||||
wrappedEmit(
|
||||
wrapEvent({
|
||||
type: EventType.IncrementalSnapshot,
|
||||
data: {
|
||||
source: IncrementalSource.Mutation,
|
||||
...m,
|
||||
},
|
||||
}),
|
||||
),
|
||||
mutationCb: wrappedMutationEmit,
|
||||
mousemoveCb: (positions, source) =>
|
||||
wrappedEmit(
|
||||
wrapEvent({
|
||||
@@ -394,6 +407,7 @@ function record<T = eventWithTime>(
|
||||
blockSelector,
|
||||
slimDOMOptions,
|
||||
iframeManager,
|
||||
shadowDomManager,
|
||||
},
|
||||
hooks,
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
import MutationBuffer from './mutation';
|
||||
import { stringify } from './stringify';
|
||||
import { IframeManager } from './iframe-manager';
|
||||
import { ShadowDomManager } from './shadow-dom-manager';
|
||||
|
||||
type WindowWithStoredMutationObserver = Window & {
|
||||
__rrMutationObserver?: MutationObserver;
|
||||
@@ -56,7 +57,7 @@ type WindowWithAngularZone = Window & {
|
||||
|
||||
export const mutationBuffers: MutationBuffer[] = [];
|
||||
|
||||
function initMutationObserver(
|
||||
export function initMutationObserver(
|
||||
cb: mutationCallBack,
|
||||
doc: Document,
|
||||
blockClass: blockClass,
|
||||
@@ -66,6 +67,8 @@ function initMutationObserver(
|
||||
recordCanvas: boolean,
|
||||
slimDOMOptions: SlimDOMOptions,
|
||||
iframeManager: IframeManager,
|
||||
shadowDomManager: ShadowDomManager,
|
||||
rootEl: Node,
|
||||
): MutationObserver {
|
||||
const mutationBuffer = new MutationBuffer();
|
||||
mutationBuffers.push(mutationBuffer);
|
||||
@@ -80,6 +83,7 @@ function initMutationObserver(
|
||||
slimDOMOptions,
|
||||
doc,
|
||||
iframeManager,
|
||||
shadowDomManager,
|
||||
);
|
||||
let mutationObserverCtor =
|
||||
window.MutationObserver ||
|
||||
@@ -109,7 +113,7 @@ function initMutationObserver(
|
||||
const observer = new mutationObserverCtor(
|
||||
mutationBuffer.processMutations.bind(mutationBuffer),
|
||||
);
|
||||
observer.observe(doc, {
|
||||
observer.observe(rootEl, {
|
||||
attributes: true,
|
||||
attributeOldValue: true,
|
||||
characterData: true,
|
||||
@@ -763,6 +767,8 @@ export function initObservers(
|
||||
o.recordCanvas,
|
||||
o.slimDOMOptions,
|
||||
o.iframeManager,
|
||||
o.shadowDomManager,
|
||||
o.doc,
|
||||
);
|
||||
const mousemoveHandler = initMoveObserver(o.mousemoveCb, o.sampling, o.doc);
|
||||
const mouseInteractionHandler = initMouseInteractionObserver(
|
||||
|
||||
43
src/record/shadow-dom-manager.ts
Normal file
43
src/record/shadow-dom-manager.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { mutationCallBack, blockClass } from '../types';
|
||||
import { MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot';
|
||||
import { IframeManager } from './iframe-manager';
|
||||
import { initMutationObserver } from './observer';
|
||||
|
||||
type BypassOptions = {
|
||||
blockClass: blockClass;
|
||||
blockSelector: string | null;
|
||||
inlineStylesheet: boolean;
|
||||
maskInputOptions: MaskInputOptions;
|
||||
recordCanvas: boolean;
|
||||
slimDOMOptions: SlimDOMOptions;
|
||||
iframeManager: IframeManager;
|
||||
};
|
||||
|
||||
export class ShadowDomManager {
|
||||
private mutationCb: mutationCallBack;
|
||||
private bypassOptions: BypassOptions;
|
||||
|
||||
constructor(options: {
|
||||
mutationCb: mutationCallBack;
|
||||
bypassOptions: BypassOptions;
|
||||
}) {
|
||||
this.mutationCb = options.mutationCb;
|
||||
this.bypassOptions = options.bypassOptions;
|
||||
}
|
||||
|
||||
public addShadowRoot(shadowRoot: ShadowRoot, doc: Document) {
|
||||
initMutationObserver(
|
||||
this.mutationCb,
|
||||
doc,
|
||||
this.bypassOptions.blockClass,
|
||||
this.bypassOptions.blockSelector,
|
||||
this.bypassOptions.inlineStylesheet,
|
||||
this.bypassOptions.maskInputOptions,
|
||||
this.bypassOptions.recordCanvas,
|
||||
this.bypassOptions.slimDOMOptions,
|
||||
this.bypassOptions.iframeManager,
|
||||
this,
|
||||
shadowRoot,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user