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

13
typings/record/iframe-manager.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
import { serializedNodeWithId, INode } from 'rrweb-snapshot';
import { mutationCallBack } from '../types';
export declare class IframeManager {
private iframes;
private mutationCb;
private loadListener?;
constructor(options: {
mutationCb: mutationCallBack;
});
addIframe(iframeEl: HTMLIFrameElement): void;
addLoadListener(cb: (iframeEl: HTMLIFrameElement) => unknown): void;
attachIframe(iframeEl: INode, childSn: serializedNodeWithId): void;
}

View File

@@ -1,7 +1,9 @@
import { MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot';
import { mutationRecord, blockClass, mutationCallBack } from '../types';
import { IframeManager } from './iframe-manager';
export default class MutationBuffer {
private frozen;
private locked;
private texts;
private attributes;
private removes;
@@ -17,7 +19,9 @@ export default class MutationBuffer {
private maskInputOptions;
private recordCanvas;
private slimDOMOptions;
init(cb: mutationCallBack, blockClass: blockClass, blockSelector: string | null, inlineStylesheet: boolean, maskInputOptions: MaskInputOptions, recordCanvas: boolean, slimDOMOptions: SlimDOMOptions): void;
private doc;
private iframeManager;
init(cb: mutationCallBack, blockClass: blockClass, blockSelector: string | null, inlineStylesheet: boolean, maskInputOptions: MaskInputOptions, recordCanvas: boolean, slimDOMOptions: SlimDOMOptions, doc: Document, iframeManager: IframeManager): void;
freeze(): void;
unfreeze(): void;
isFrozen(): boolean;

View File

@@ -1,5 +1,5 @@
import { observerParam, listenerHandler, hooksParam } from '../types';
import MutationBuffer from './mutation';
export declare const mutationBuffer: MutationBuffer;
export declare const mutationBuffers: MutationBuffer[];
export declare const INPUT_TAGS: string[];
export declare function initObservers(o: observerParam, hooks?: hooksParam): listenerHandler;

View File

@@ -19,6 +19,7 @@ export declare class Replayer {
private fragmentParentMap;
private elementStateMap;
private imageMap;
private newDocumentQueue;
constructor(events: Array<eventWithTime | string>, config?: Partial<playerConfig>);
on(event: string, handler: Handler): this;
setConfig(config: Partial<playerConfig>): void;
@@ -36,6 +37,9 @@ export declare class Replayer {
private handleResize;
private getCastFn;
private rebuildFullSnapshot;
private insertStyleRules;
private attachDocumentToIframe;
private collectIframeAndAttachDocument;
private waitForStylesheetLoad;
private preloadAllImages;
private applyIncremental;

13
typings/types.d.ts vendored
View File

@@ -2,6 +2,7 @@
import { serializedNodeWithId, idNodeMap, INode, MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot';
import { PackFn, UnpackFn } from './packer/base';
import { FontFaceDescriptors } from 'css-font-loading-module';
import { IframeManager } from './record/iframe-manager';
export declare enum EventType {
DomContentLoaded = 0,
Load = 1,
@@ -81,7 +82,7 @@ export declare type scrollData = {
} & scrollPosition;
export declare type viewportResizeData = {
source: IncrementalSource.ViewportResize;
} & viewportResizeDimention;
} & viewportResizeDimension;
export declare type inputData = {
source: IncrementalSource.Input;
id: number;
@@ -157,6 +158,8 @@ export declare type observerParam = {
recordCanvas: boolean;
collectFonts: boolean;
slimDOMOptions: SlimDOMOptions;
doc: Document;
iframeManager: IframeManager;
};
export declare type hooksParam = {
mutation?: mutationCallBack;
@@ -304,11 +307,11 @@ export declare type LogParam = {
};
export declare type fontCallback = (p: fontParam) => void;
export declare type logCallback = (p: LogParam) => void;
export declare type viewportResizeDimention = {
export declare type viewportResizeDimension = {
width: number;
height: number;
};
export declare type viewportResizeCallback = (d: viewportResizeDimention) => void;
export declare type viewportResizeCallback = (d: viewportResizeDimension) => void;
export declare type inputValue = {
text: string;
isChecked: boolean;
@@ -325,6 +328,10 @@ export declare type mediaInteractionParam = {
id: number;
};
export declare type mediaInteractionCallback = (p: mediaInteractionParam) => void;
export declare type DocumentDimension = {
x: number;
y: number;
};
export declare type Mirror = {
map: idNodeMap;
getId: (n: INode) => number;

13
typings/utils.d.ts vendored
View File

@@ -1,5 +1,5 @@
import { Mirror, throttleOptions, listenerHandler, hookResetter, blockClass, eventWithTime, addedNodeMutation, removedNodeMutation, textMutation, attributeMutation, mutationData, scrollData, inputData } from './types';
import { INode } from 'rrweb-snapshot';
import { Mirror, throttleOptions, listenerHandler, hookResetter, blockClass, eventWithTime, addedNodeMutation, removedNodeMutation, textMutation, attributeMutation, mutationData, scrollData, inputData, DocumentDimension } from './types';
import { INode, serializedNodeWithId } from 'rrweb-snapshot';
export declare function on(type: string, fn: EventListenerOrEventListenerObject, target?: Document | Window): listenerHandler;
export declare const mirror: Mirror;
export declare function throttle<T>(func: (arg: T) => void, wait: number, options?: throttleOptions): (arg: T) => void;
@@ -53,4 +53,13 @@ declare type ResolveTree = {
};
export declare function queueToResolveTrees(queue: addedNodeMutation[]): ResolveTree[];
export declare function iterateResolveTree(tree: ResolveTree, cb: (mutation: addedNodeMutation) => unknown): void;
declare type HTMLIFrameINode = HTMLIFrameElement & {
__sn: serializedNodeWithId;
};
export declare type AppendedIframe = {
mutationInQueue: addedNodeMutation;
builtNode: HTMLIFrameINode;
};
export declare function isIframeINode(node: INode): node is HTMLIFrameINode;
export declare function getBaseDimension(node: Node): DocumentDimension;
export {};