Impl record iframe (#481)

* Impl record iframe

* iframe observe

* temp: add bundle file to git

* update bundle

* update with pick

* update bundle

* fix fragment map remove

* feat: add an option to determine whether to pause CSS animation when playback is paused (#428)

set pauseAnimation to true by default

* fix: elements would lose some states like scroll position because of "virtual parent" optimization (#427)

* fix: elements would lose some state like scroll position because of "virtual parent" optimization

* refactor: the bugfix code

bug: elements would lose some state like scroll position because of "virtual parent" optimization

* fix: an error occured at applyMutation(remove nodes part)

error message:
Uncaught (in promise) DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node

* pick fixes

* revert ignore file

* re-impl iframe record

* re-impl iframe replay

* code housekeeping

* move multi layer dimension calculation to replay side

* update test cases

* teardown test server

* upgrade rrweb-snapshot with iframe load timeout

Co-authored-by: Lucky Feng <yun.feng@smartx.com>
This commit is contained in:
yz-yu
2026-04-01 12:00:00 +08:00
committed by GitHub
parent b99e843e2a
commit 33f0ac5cfe
24 changed files with 1414 additions and 246 deletions

View File

@@ -5,6 +5,7 @@ import {
MaskInputOptions,
SlimDOMOptions,
IGNORED_NODE,
NodeType,
} from 'rrweb-snapshot';
import {
mutationRecord,
@@ -16,6 +17,7 @@ import {
addedNodeMutation,
} from '../types';
import { mirror, isBlocked, isAncestorRemoved, isIgnored } from '../utils';
import { IframeManager } from './iframe-manager';
type DoubleLinkedListNode = {
previous: DoubleLinkedListNode | null;
@@ -149,6 +151,9 @@ export default class MutationBuffer {
private maskInputOptions: MaskInputOptions;
private recordCanvas: boolean;
private slimDOMOptions: SlimDOMOptions;
private doc: Document;
private iframeManager: IframeManager;
public init(
cb: mutationCallBack,
@@ -158,6 +163,8 @@ export default class MutationBuffer {
maskInputOptions: MaskInputOptions,
recordCanvas: boolean,
slimDOMOptions: SlimDOMOptions,
doc: Document,
iframeManager: IframeManager,
) {
this.blockClass = blockClass;
this.blockSelector = blockSelector;
@@ -166,6 +173,8 @@ export default class MutationBuffer {
this.recordCanvas = recordCanvas;
this.slimDOMOptions = slimDOMOptions;
this.emissionCallback = cb;
this.doc = doc;
this.iframeManager = iframeManager;
}
public freeze() {
@@ -223,7 +232,7 @@ export default class MutationBuffer {
return nextId;
};
const pushAdd = (n: Node) => {
if (!n.parentNode || !document.contains(n)) {
if (!n.parentNode || !this.doc.contains(n)) {
return;
}
const parentId = mirror.getId((n.parentNode as Node) as INode);
@@ -232,7 +241,7 @@ export default class MutationBuffer {
return addList.addNode(n);
}
let sn = serializeNodeWithId(n, {
doc: document,
doc: this.doc,
map: mirror.map,
blockClass: this.blockClass,
blockSelector: this.blockSelector,
@@ -241,6 +250,19 @@ export default class MutationBuffer {
maskInputOptions: this.maskInputOptions,
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,
);
}
},
onIframeLoad: (iframe, childSn) => {
this.iframeManager.attachIframe(iframe, childSn);
},
});
if (sn) {
adds.push({
@@ -391,7 +413,7 @@ export default class MutationBuffer {
}
// overwrite attribute if the mutations was triggered in same time
item.attributes[m.attributeName!] = transformAttribute(
document,
this.doc,
m.attributeName!,
value!,
);