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:
@@ -62,6 +62,6 @@
|
|||||||
"@xstate/fsm": "^1.4.0",
|
"@xstate/fsm": "^1.4.0",
|
||||||
"fflate": "^0.4.4",
|
"fflate": "^0.4.4",
|
||||||
"mitt": "^1.1.3",
|
"mitt": "^1.1.3",
|
||||||
"rrweb-snapshot": "^1.0.4"
|
"rrweb-snapshot": "^1.0.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,11 @@ function getCode(): string {
|
|||||||
width: 1600,
|
width: 1600,
|
||||||
height: 900,
|
height: 900,
|
||||||
},
|
},
|
||||||
args: ['--start-maximized', '--ignore-certificate-errors'],
|
args: [
|
||||||
|
'--start-maximized',
|
||||||
|
'--ignore-certificate-errors',
|
||||||
|
'--no-sandbox',
|
||||||
|
],
|
||||||
});
|
});
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
await page.goto(url, {
|
await page.goto(url, {
|
||||||
@@ -128,7 +132,7 @@ function getCode(): string {
|
|||||||
width: 1600,
|
width: 1600,
|
||||||
height: 900,
|
height: 900,
|
||||||
},
|
},
|
||||||
args: ['--start-maximized'],
|
args: ['--start-maximized', '--no-sandbox'],
|
||||||
});
|
});
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
await page.goto('about:blank');
|
await page.goto('about:blank');
|
||||||
|
|||||||
36
src/record/iframe-manager.ts
Normal file
36
src/record/iframe-manager.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { serializedNodeWithId, INode } from 'rrweb-snapshot';
|
||||||
|
import { mutationCallBack } from '../types';
|
||||||
|
|
||||||
|
export class IframeManager {
|
||||||
|
private iframes: WeakMap<HTMLIFrameElement, true> = new WeakMap();
|
||||||
|
private mutationCb: mutationCallBack;
|
||||||
|
private loadListener?: (iframeEl: HTMLIFrameElement) => unknown;
|
||||||
|
|
||||||
|
constructor(options: { mutationCb: mutationCallBack }) {
|
||||||
|
this.mutationCb = options.mutationCb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public addIframe(iframeEl: HTMLIFrameElement) {
|
||||||
|
this.iframes.set(iframeEl, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addLoadListener(cb: (iframeEl: HTMLIFrameElement) => unknown) {
|
||||||
|
this.loadListener = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public attachIframe(iframeEl: INode, childSn: serializedNodeWithId) {
|
||||||
|
this.mutationCb({
|
||||||
|
adds: [
|
||||||
|
{
|
||||||
|
parentId: iframeEl.__sn.id,
|
||||||
|
nextId: null,
|
||||||
|
node: childSn,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
removes: [],
|
||||||
|
texts: [],
|
||||||
|
attributes: [],
|
||||||
|
});
|
||||||
|
this.loadListener?.((iframeEl as unknown) as HTMLIFrameElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
import { snapshot, MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot';
|
import { snapshot, MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot';
|
||||||
import { initObservers, mutationBuffer } from './observer';
|
import { initObservers, mutationBuffers } from './observer';
|
||||||
import {
|
import {
|
||||||
mirror,
|
mirror,
|
||||||
on,
|
on,
|
||||||
getWindowWidth,
|
getWindowWidth,
|
||||||
getWindowHeight,
|
getWindowHeight,
|
||||||
polyfill,
|
polyfill,
|
||||||
|
isIframeINode,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import {
|
import {
|
||||||
EventType,
|
EventType,
|
||||||
@@ -16,6 +17,7 @@ import {
|
|||||||
listenerHandler,
|
listenerHandler,
|
||||||
LogRecordOptions,
|
LogRecordOptions,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
import { IframeManager } from './iframe-manager';
|
||||||
|
|
||||||
function wrapEvent(e: event): eventWithTime {
|
function wrapEvent(e: event): eventWithTime {
|
||||||
return {
|
return {
|
||||||
@@ -138,7 +140,7 @@ function record<T = eventWithTime>(
|
|||||||
let incrementalSnapshotCount = 0;
|
let incrementalSnapshotCount = 0;
|
||||||
wrappedEmit = (e: eventWithTime, isCheckout?: boolean) => {
|
wrappedEmit = (e: eventWithTime, isCheckout?: boolean) => {
|
||||||
if (
|
if (
|
||||||
mutationBuffer.isFrozen() &&
|
mutationBuffers[0]?.isFrozen() &&
|
||||||
e.type !== EventType.FullSnapshot &&
|
e.type !== EventType.FullSnapshot &&
|
||||||
!(
|
!(
|
||||||
e.type === EventType.IncrementalSnapshot &&
|
e.type === EventType.IncrementalSnapshot &&
|
||||||
@@ -147,7 +149,7 @@ function record<T = eventWithTime>(
|
|||||||
) {
|
) {
|
||||||
// we've got a user initiated event so first we need to apply
|
// we've got a user initiated event so first we need to apply
|
||||||
// all DOM changes that have been buffering during paused state
|
// all DOM changes that have been buffering during paused state
|
||||||
mutationBuffer.unfreeze();
|
mutationBuffers.forEach((buf) => buf.unfreeze());
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(((packFn ? packFn(e) : e) as unknown) as T, isCheckout);
|
emit(((packFn ? packFn(e) : e) as unknown) as T, isCheckout);
|
||||||
@@ -167,6 +169,19 @@ function record<T = eventWithTime>(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const iframeManager = new IframeManager({
|
||||||
|
mutationCb: (m) =>
|
||||||
|
wrappedEmit(
|
||||||
|
wrapEvent({
|
||||||
|
type: EventType.IncrementalSnapshot,
|
||||||
|
data: {
|
||||||
|
source: IncrementalSource.Mutation,
|
||||||
|
...m,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
function takeFullSnapshot(isCheckout = false) {
|
function takeFullSnapshot(isCheckout = false) {
|
||||||
wrappedEmit(
|
wrappedEmit(
|
||||||
wrapEvent({
|
wrapEvent({
|
||||||
@@ -180,8 +195,7 @@ function record<T = eventWithTime>(
|
|||||||
isCheckout,
|
isCheckout,
|
||||||
);
|
);
|
||||||
|
|
||||||
let wasFrozen = mutationBuffer.isFrozen();
|
mutationBuffers.forEach((buf) => buf.lock()); // don't allow any mirror modifications during snapshotting
|
||||||
mutationBuffer.lock(); // don't allow any mirror modifications during snapshotting
|
|
||||||
const [node, idNodeMap] = snapshot(document, {
|
const [node, idNodeMap] = snapshot(document, {
|
||||||
blockClass,
|
blockClass,
|
||||||
blockSelector,
|
blockSelector,
|
||||||
@@ -189,6 +203,14 @@ function record<T = eventWithTime>(
|
|||||||
maskAllInputs: maskInputOptions,
|
maskAllInputs: maskInputOptions,
|
||||||
slimDOM: slimDOMOptions,
|
slimDOM: slimDOMOptions,
|
||||||
recordCanvas,
|
recordCanvas,
|
||||||
|
onSerialize: (n) => {
|
||||||
|
if (isIframeINode(n)) {
|
||||||
|
iframeManager.addIframe(n);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onIframeLoad: (iframe, childSn) => {
|
||||||
|
iframeManager.attachIframe(iframe, childSn);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
@@ -220,7 +242,7 @@ function record<T = eventWithTime>(
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
mutationBuffer.unlock(); // generate & emit any mutations that happened during snapshotting, as can now apply against the newly built mirror
|
mutationBuffers.forEach((buf) => buf.unlock()); // generate & emit any mutations that happened during snapshotting, as can now apply against the newly built mirror
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -235,137 +257,145 @@ function record<T = eventWithTime>(
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const observe = (doc: Document) => {
|
||||||
|
return initObservers(
|
||||||
|
{
|
||||||
|
mutationCb: (m) =>
|
||||||
|
wrappedEmit(
|
||||||
|
wrapEvent({
|
||||||
|
type: EventType.IncrementalSnapshot,
|
||||||
|
data: {
|
||||||
|
source: IncrementalSource.Mutation,
|
||||||
|
...m,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
mousemoveCb: (positions, source) =>
|
||||||
|
wrappedEmit(
|
||||||
|
wrapEvent({
|
||||||
|
type: EventType.IncrementalSnapshot,
|
||||||
|
data: {
|
||||||
|
source,
|
||||||
|
positions,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
mouseInteractionCb: (d) =>
|
||||||
|
wrappedEmit(
|
||||||
|
wrapEvent({
|
||||||
|
type: EventType.IncrementalSnapshot,
|
||||||
|
data: {
|
||||||
|
source: IncrementalSource.MouseInteraction,
|
||||||
|
...d,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
scrollCb: (p) =>
|
||||||
|
wrappedEmit(
|
||||||
|
wrapEvent({
|
||||||
|
type: EventType.IncrementalSnapshot,
|
||||||
|
data: {
|
||||||
|
source: IncrementalSource.Scroll,
|
||||||
|
...p,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
viewportResizeCb: (d) =>
|
||||||
|
wrappedEmit(
|
||||||
|
wrapEvent({
|
||||||
|
type: EventType.IncrementalSnapshot,
|
||||||
|
data: {
|
||||||
|
source: IncrementalSource.ViewportResize,
|
||||||
|
...d,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
inputCb: (v) =>
|
||||||
|
wrappedEmit(
|
||||||
|
wrapEvent({
|
||||||
|
type: EventType.IncrementalSnapshot,
|
||||||
|
data: {
|
||||||
|
source: IncrementalSource.Input,
|
||||||
|
...v,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
mediaInteractionCb: (p) =>
|
||||||
|
wrappedEmit(
|
||||||
|
wrapEvent({
|
||||||
|
type: EventType.IncrementalSnapshot,
|
||||||
|
data: {
|
||||||
|
source: IncrementalSource.MediaInteraction,
|
||||||
|
...p,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
styleSheetRuleCb: (r) =>
|
||||||
|
wrappedEmit(
|
||||||
|
wrapEvent({
|
||||||
|
type: EventType.IncrementalSnapshot,
|
||||||
|
data: {
|
||||||
|
source: IncrementalSource.StyleSheetRule,
|
||||||
|
...r,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
canvasMutationCb: (p) =>
|
||||||
|
wrappedEmit(
|
||||||
|
wrapEvent({
|
||||||
|
type: EventType.IncrementalSnapshot,
|
||||||
|
data: {
|
||||||
|
source: IncrementalSource.CanvasMutation,
|
||||||
|
...p,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
fontCb: (p) =>
|
||||||
|
wrappedEmit(
|
||||||
|
wrapEvent({
|
||||||
|
type: EventType.IncrementalSnapshot,
|
||||||
|
data: {
|
||||||
|
source: IncrementalSource.Font,
|
||||||
|
...p,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
logCb: (p) =>
|
||||||
|
wrappedEmit(
|
||||||
|
wrapEvent({
|
||||||
|
type: EventType.IncrementalSnapshot,
|
||||||
|
data: {
|
||||||
|
source: IncrementalSource.Log,
|
||||||
|
...p,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
blockClass,
|
||||||
|
ignoreClass,
|
||||||
|
maskInputOptions,
|
||||||
|
inlineStylesheet,
|
||||||
|
sampling,
|
||||||
|
recordCanvas,
|
||||||
|
collectFonts,
|
||||||
|
doc,
|
||||||
|
maskInputFn,
|
||||||
|
logOptions,
|
||||||
|
blockSelector,
|
||||||
|
slimDOMOptions,
|
||||||
|
iframeManager,
|
||||||
|
},
|
||||||
|
hooks,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
iframeManager.addLoadListener((iframeEl) => {
|
||||||
|
handlers.push(observe(iframeEl.contentDocument!));
|
||||||
|
});
|
||||||
|
|
||||||
const init = () => {
|
const init = () => {
|
||||||
takeFullSnapshot();
|
takeFullSnapshot();
|
||||||
|
handlers.push(observe(document));
|
||||||
handlers.push(
|
|
||||||
initObservers(
|
|
||||||
{
|
|
||||||
mutationCb: (m) =>
|
|
||||||
wrappedEmit(
|
|
||||||
wrapEvent({
|
|
||||||
type: EventType.IncrementalSnapshot,
|
|
||||||
data: {
|
|
||||||
source: IncrementalSource.Mutation,
|
|
||||||
...m,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
mousemoveCb: (positions, source) =>
|
|
||||||
wrappedEmit(
|
|
||||||
wrapEvent({
|
|
||||||
type: EventType.IncrementalSnapshot,
|
|
||||||
data: {
|
|
||||||
source,
|
|
||||||
positions,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
mouseInteractionCb: (d) =>
|
|
||||||
wrappedEmit(
|
|
||||||
wrapEvent({
|
|
||||||
type: EventType.IncrementalSnapshot,
|
|
||||||
data: {
|
|
||||||
source: IncrementalSource.MouseInteraction,
|
|
||||||
...d,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
scrollCb: (p) =>
|
|
||||||
wrappedEmit(
|
|
||||||
wrapEvent({
|
|
||||||
type: EventType.IncrementalSnapshot,
|
|
||||||
data: {
|
|
||||||
source: IncrementalSource.Scroll,
|
|
||||||
...p,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
viewportResizeCb: (d) =>
|
|
||||||
wrappedEmit(
|
|
||||||
wrapEvent({
|
|
||||||
type: EventType.IncrementalSnapshot,
|
|
||||||
data: {
|
|
||||||
source: IncrementalSource.ViewportResize,
|
|
||||||
...d,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
inputCb: (v) =>
|
|
||||||
wrappedEmit(
|
|
||||||
wrapEvent({
|
|
||||||
type: EventType.IncrementalSnapshot,
|
|
||||||
data: {
|
|
||||||
source: IncrementalSource.Input,
|
|
||||||
...v,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
mediaInteractionCb: (p) =>
|
|
||||||
wrappedEmit(
|
|
||||||
wrapEvent({
|
|
||||||
type: EventType.IncrementalSnapshot,
|
|
||||||
data: {
|
|
||||||
source: IncrementalSource.MediaInteraction,
|
|
||||||
...p,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
styleSheetRuleCb: (r) =>
|
|
||||||
wrappedEmit(
|
|
||||||
wrapEvent({
|
|
||||||
type: EventType.IncrementalSnapshot,
|
|
||||||
data: {
|
|
||||||
source: IncrementalSource.StyleSheetRule,
|
|
||||||
...r,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
canvasMutationCb: (p) =>
|
|
||||||
wrappedEmit(
|
|
||||||
wrapEvent({
|
|
||||||
type: EventType.IncrementalSnapshot,
|
|
||||||
data: {
|
|
||||||
source: IncrementalSource.CanvasMutation,
|
|
||||||
...p,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
fontCb: (p) =>
|
|
||||||
wrappedEmit(
|
|
||||||
wrapEvent({
|
|
||||||
type: EventType.IncrementalSnapshot,
|
|
||||||
data: {
|
|
||||||
source: IncrementalSource.Font,
|
|
||||||
...p,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
logCb: (p) =>
|
|
||||||
wrappedEmit(
|
|
||||||
wrapEvent({
|
|
||||||
type: EventType.IncrementalSnapshot,
|
|
||||||
data: {
|
|
||||||
source: IncrementalSource.Log,
|
|
||||||
...p,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
blockClass,
|
|
||||||
blockSelector,
|
|
||||||
ignoreClass,
|
|
||||||
maskInputOptions,
|
|
||||||
maskInputFn,
|
|
||||||
inlineStylesheet,
|
|
||||||
sampling,
|
|
||||||
recordCanvas,
|
|
||||||
collectFonts,
|
|
||||||
slimDOMOptions,
|
|
||||||
logOptions,
|
|
||||||
},
|
|
||||||
hooks,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
if (
|
if (
|
||||||
document.readyState === 'interactive' ||
|
document.readyState === 'interactive' ||
|
||||||
@@ -414,7 +444,7 @@ record.addCustomEvent = <T>(tag: string, payload: T) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
record.freezePage = () => {
|
record.freezePage = () => {
|
||||||
mutationBuffer.freeze();
|
mutationBuffers.forEach((buf) => buf.freeze());
|
||||||
};
|
};
|
||||||
|
|
||||||
export default record;
|
export default record;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
MaskInputOptions,
|
MaskInputOptions,
|
||||||
SlimDOMOptions,
|
SlimDOMOptions,
|
||||||
IGNORED_NODE,
|
IGNORED_NODE,
|
||||||
|
NodeType,
|
||||||
} from 'rrweb-snapshot';
|
} from 'rrweb-snapshot';
|
||||||
import {
|
import {
|
||||||
mutationRecord,
|
mutationRecord,
|
||||||
@@ -16,6 +17,7 @@ import {
|
|||||||
addedNodeMutation,
|
addedNodeMutation,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { mirror, isBlocked, isAncestorRemoved, isIgnored } from '../utils';
|
import { mirror, isBlocked, isAncestorRemoved, isIgnored } from '../utils';
|
||||||
|
import { IframeManager } from './iframe-manager';
|
||||||
|
|
||||||
type DoubleLinkedListNode = {
|
type DoubleLinkedListNode = {
|
||||||
previous: DoubleLinkedListNode | null;
|
previous: DoubleLinkedListNode | null;
|
||||||
@@ -149,6 +151,9 @@ export default class MutationBuffer {
|
|||||||
private maskInputOptions: MaskInputOptions;
|
private maskInputOptions: MaskInputOptions;
|
||||||
private recordCanvas: boolean;
|
private recordCanvas: boolean;
|
||||||
private slimDOMOptions: SlimDOMOptions;
|
private slimDOMOptions: SlimDOMOptions;
|
||||||
|
private doc: Document;
|
||||||
|
|
||||||
|
private iframeManager: IframeManager;
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
cb: mutationCallBack,
|
cb: mutationCallBack,
|
||||||
@@ -158,6 +163,8 @@ export default class MutationBuffer {
|
|||||||
maskInputOptions: MaskInputOptions,
|
maskInputOptions: MaskInputOptions,
|
||||||
recordCanvas: boolean,
|
recordCanvas: boolean,
|
||||||
slimDOMOptions: SlimDOMOptions,
|
slimDOMOptions: SlimDOMOptions,
|
||||||
|
doc: Document,
|
||||||
|
iframeManager: IframeManager,
|
||||||
) {
|
) {
|
||||||
this.blockClass = blockClass;
|
this.blockClass = blockClass;
|
||||||
this.blockSelector = blockSelector;
|
this.blockSelector = blockSelector;
|
||||||
@@ -166,6 +173,8 @@ export default class MutationBuffer {
|
|||||||
this.recordCanvas = recordCanvas;
|
this.recordCanvas = recordCanvas;
|
||||||
this.slimDOMOptions = slimDOMOptions;
|
this.slimDOMOptions = slimDOMOptions;
|
||||||
this.emissionCallback = cb;
|
this.emissionCallback = cb;
|
||||||
|
this.doc = doc;
|
||||||
|
this.iframeManager = iframeManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public freeze() {
|
public freeze() {
|
||||||
@@ -223,7 +232,7 @@ export default class MutationBuffer {
|
|||||||
return nextId;
|
return nextId;
|
||||||
};
|
};
|
||||||
const pushAdd = (n: Node) => {
|
const pushAdd = (n: Node) => {
|
||||||
if (!n.parentNode || !document.contains(n)) {
|
if (!n.parentNode || !this.doc.contains(n)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const parentId = mirror.getId((n.parentNode as Node) as INode);
|
const parentId = mirror.getId((n.parentNode as Node) as INode);
|
||||||
@@ -232,7 +241,7 @@ export default class MutationBuffer {
|
|||||||
return addList.addNode(n);
|
return addList.addNode(n);
|
||||||
}
|
}
|
||||||
let sn = serializeNodeWithId(n, {
|
let sn = serializeNodeWithId(n, {
|
||||||
doc: document,
|
doc: this.doc,
|
||||||
map: mirror.map,
|
map: mirror.map,
|
||||||
blockClass: this.blockClass,
|
blockClass: this.blockClass,
|
||||||
blockSelector: this.blockSelector,
|
blockSelector: this.blockSelector,
|
||||||
@@ -241,6 +250,19 @@ export default class MutationBuffer {
|
|||||||
maskInputOptions: this.maskInputOptions,
|
maskInputOptions: this.maskInputOptions,
|
||||||
slimDOMOptions: this.slimDOMOptions,
|
slimDOMOptions: this.slimDOMOptions,
|
||||||
recordCanvas: this.recordCanvas,
|
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) {
|
if (sn) {
|
||||||
adds.push({
|
adds.push({
|
||||||
@@ -391,7 +413,7 @@ export default class MutationBuffer {
|
|||||||
}
|
}
|
||||||
// overwrite attribute if the mutations was triggered in same time
|
// overwrite attribute if the mutations was triggered in same time
|
||||||
item.attributes[m.attributeName!] = transformAttribute(
|
item.attributes[m.attributeName!] = transformAttribute(
|
||||||
document,
|
this.doc,
|
||||||
m.attributeName!,
|
m.attributeName!,
|
||||||
value!,
|
value!,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import {
|
|||||||
} from '../types';
|
} from '../types';
|
||||||
import MutationBuffer from './mutation';
|
import MutationBuffer from './mutation';
|
||||||
import { stringify } from './stringify';
|
import { stringify } from './stringify';
|
||||||
|
import { IframeManager } from './iframe-manager';
|
||||||
|
|
||||||
type WindowWithStoredMutationObserver = Window & {
|
type WindowWithStoredMutationObserver = Window & {
|
||||||
__rrMutationObserver?: MutationObserver;
|
__rrMutationObserver?: MutationObserver;
|
||||||
@@ -53,17 +54,21 @@ type WindowWithAngularZone = Window & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mutationBuffer = new MutationBuffer();
|
export const mutationBuffers: MutationBuffer[] = [];
|
||||||
|
|
||||||
function initMutationObserver(
|
function initMutationObserver(
|
||||||
cb: mutationCallBack,
|
cb: mutationCallBack,
|
||||||
|
doc: Document,
|
||||||
blockClass: blockClass,
|
blockClass: blockClass,
|
||||||
blockSelector: string | null,
|
blockSelector: string | null,
|
||||||
inlineStylesheet: boolean,
|
inlineStylesheet: boolean,
|
||||||
maskInputOptions: MaskInputOptions,
|
maskInputOptions: MaskInputOptions,
|
||||||
recordCanvas: boolean,
|
recordCanvas: boolean,
|
||||||
slimDOMOptions: SlimDOMOptions,
|
slimDOMOptions: SlimDOMOptions,
|
||||||
|
iframeManager: IframeManager,
|
||||||
): MutationObserver {
|
): MutationObserver {
|
||||||
|
const mutationBuffer = new MutationBuffer();
|
||||||
|
mutationBuffers.push(mutationBuffer);
|
||||||
// see mutation.ts for details
|
// see mutation.ts for details
|
||||||
mutationBuffer.init(
|
mutationBuffer.init(
|
||||||
cb,
|
cb,
|
||||||
@@ -73,8 +78,10 @@ function initMutationObserver(
|
|||||||
maskInputOptions,
|
maskInputOptions,
|
||||||
recordCanvas,
|
recordCanvas,
|
||||||
slimDOMOptions,
|
slimDOMOptions,
|
||||||
|
doc,
|
||||||
|
iframeManager,
|
||||||
);
|
);
|
||||||
let mutationBufferCtor =
|
let mutationObserverCtor =
|
||||||
window.MutationObserver ||
|
window.MutationObserver ||
|
||||||
/**
|
/**
|
||||||
* Some websites may disable MutationObserver by removing it from the window object.
|
* Some websites may disable MutationObserver by removing it from the window object.
|
||||||
@@ -94,15 +101,15 @@ function initMutationObserver(
|
|||||||
angularZoneSymbol
|
angularZoneSymbol
|
||||||
]
|
]
|
||||||
) {
|
) {
|
||||||
mutationBufferCtor = ((window as unknown) as Record<
|
mutationObserverCtor = ((window as unknown) as Record<
|
||||||
string,
|
string,
|
||||||
typeof MutationObserver
|
typeof MutationObserver
|
||||||
>)[angularZoneSymbol];
|
>)[angularZoneSymbol];
|
||||||
}
|
}
|
||||||
const observer = new mutationBufferCtor(
|
const observer = new mutationObserverCtor(
|
||||||
mutationBuffer.processMutations.bind(mutationBuffer),
|
mutationBuffer.processMutations.bind(mutationBuffer),
|
||||||
);
|
);
|
||||||
observer.observe(document, {
|
observer.observe(doc, {
|
||||||
attributes: true,
|
attributes: true,
|
||||||
attributeOldValue: true,
|
attributeOldValue: true,
|
||||||
characterData: true,
|
characterData: true,
|
||||||
@@ -116,6 +123,7 @@ function initMutationObserver(
|
|||||||
function initMoveObserver(
|
function initMoveObserver(
|
||||||
cb: mousemoveCallBack,
|
cb: mousemoveCallBack,
|
||||||
sampling: SamplingStrategy,
|
sampling: SamplingStrategy,
|
||||||
|
doc: Document,
|
||||||
): listenerHandler {
|
): listenerHandler {
|
||||||
if (sampling.mousemove === false) {
|
if (sampling.mousemove === false) {
|
||||||
return () => {};
|
return () => {};
|
||||||
@@ -161,8 +169,8 @@ function initMoveObserver(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
const handlers = [
|
const handlers = [
|
||||||
on('mousemove', updatePosition),
|
on('mousemove', updatePosition, doc),
|
||||||
on('touchmove', updatePosition),
|
on('touchmove', updatePosition, doc),
|
||||||
];
|
];
|
||||||
return () => {
|
return () => {
|
||||||
handlers.forEach((h) => h());
|
handlers.forEach((h) => h());
|
||||||
@@ -171,6 +179,7 @@ function initMoveObserver(
|
|||||||
|
|
||||||
function initMouseInteractionObserver(
|
function initMouseInteractionObserver(
|
||||||
cb: mouseInteractionCallBack,
|
cb: mouseInteractionCallBack,
|
||||||
|
doc: Document,
|
||||||
blockClass: blockClass,
|
blockClass: blockClass,
|
||||||
sampling: SamplingStrategy,
|
sampling: SamplingStrategy,
|
||||||
): listenerHandler {
|
): listenerHandler {
|
||||||
@@ -211,7 +220,7 @@ function initMouseInteractionObserver(
|
|||||||
.forEach((eventKey: keyof typeof MouseInteractions) => {
|
.forEach((eventKey: keyof typeof MouseInteractions) => {
|
||||||
const eventName = eventKey.toLowerCase();
|
const eventName = eventKey.toLowerCase();
|
||||||
const handler = getHandler(eventKey);
|
const handler = getHandler(eventKey);
|
||||||
handlers.push(on(eventName, handler));
|
handlers.push(on(eventName, handler, doc));
|
||||||
});
|
});
|
||||||
return () => {
|
return () => {
|
||||||
handlers.forEach((h) => h());
|
handlers.forEach((h) => h());
|
||||||
@@ -220,6 +229,7 @@ function initMouseInteractionObserver(
|
|||||||
|
|
||||||
function initScrollObserver(
|
function initScrollObserver(
|
||||||
cb: scrollCallback,
|
cb: scrollCallback,
|
||||||
|
doc: Document,
|
||||||
blockClass: blockClass,
|
blockClass: blockClass,
|
||||||
sampling: SamplingStrategy,
|
sampling: SamplingStrategy,
|
||||||
): listenerHandler {
|
): listenerHandler {
|
||||||
@@ -228,8 +238,8 @@ function initScrollObserver(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const id = mirror.getId(evt.target as INode);
|
const id = mirror.getId(evt.target as INode);
|
||||||
if (evt.target === document) {
|
if (evt.target === doc) {
|
||||||
const scrollEl = (document.scrollingElement || document.documentElement)!;
|
const scrollEl = (doc.scrollingElement || doc.documentElement)!;
|
||||||
cb({
|
cb({
|
||||||
id,
|
id,
|
||||||
x: scrollEl.scrollLeft,
|
x: scrollEl.scrollLeft,
|
||||||
@@ -270,6 +280,7 @@ export const INPUT_TAGS = ['INPUT', 'TEXTAREA', 'SELECT'];
|
|||||||
const lastInputValueMap: WeakMap<EventTarget, inputValue> = new WeakMap();
|
const lastInputValueMap: WeakMap<EventTarget, inputValue> = new WeakMap();
|
||||||
function initInputObserver(
|
function initInputObserver(
|
||||||
cb: inputCallback,
|
cb: inputCallback,
|
||||||
|
doc: Document,
|
||||||
blockClass: blockClass,
|
blockClass: blockClass,
|
||||||
ignoreClass: string,
|
ignoreClass: string,
|
||||||
maskInputOptions: MaskInputOptions,
|
maskInputOptions: MaskInputOptions,
|
||||||
@@ -314,7 +325,7 @@ function initInputObserver(
|
|||||||
// the other radios with the same name attribute will be unchecked.
|
// the other radios with the same name attribute will be unchecked.
|
||||||
const name: string | undefined = (target as HTMLInputElement).name;
|
const name: string | undefined = (target as HTMLInputElement).name;
|
||||||
if (type === 'radio' && name && isChecked) {
|
if (type === 'radio' && name && isChecked) {
|
||||||
document
|
doc
|
||||||
.querySelectorAll(`input[type="radio"][name="${name}"]`)
|
.querySelectorAll(`input[type="radio"][name="${name}"]`)
|
||||||
.forEach((el) => {
|
.forEach((el) => {
|
||||||
if (el !== target) {
|
if (el !== target) {
|
||||||
@@ -344,7 +355,7 @@ function initInputObserver(
|
|||||||
const events = sampling.input === 'last' ? ['change'] : ['input', 'change'];
|
const events = sampling.input === 'last' ? ['change'] : ['input', 'change'];
|
||||||
const handlers: Array<
|
const handlers: Array<
|
||||||
listenerHandler | hookResetter
|
listenerHandler | hookResetter
|
||||||
> = events.map((eventName) => on(eventName, eventHandler));
|
> = events.map((eventName) => on(eventName, eventHandler, doc));
|
||||||
const propertyDescriptor = Object.getOwnPropertyDescriptor(
|
const propertyDescriptor = Object.getOwnPropertyDescriptor(
|
||||||
HTMLInputElement.prototype,
|
HTMLInputElement.prototype,
|
||||||
'value',
|
'value',
|
||||||
@@ -727,27 +738,32 @@ export function initObservers(
|
|||||||
mergeHooks(o, hooks);
|
mergeHooks(o, hooks);
|
||||||
const mutationObserver = initMutationObserver(
|
const mutationObserver = initMutationObserver(
|
||||||
o.mutationCb,
|
o.mutationCb,
|
||||||
|
o.doc,
|
||||||
o.blockClass,
|
o.blockClass,
|
||||||
o.blockSelector,
|
o.blockSelector,
|
||||||
o.inlineStylesheet,
|
o.inlineStylesheet,
|
||||||
o.maskInputOptions,
|
o.maskInputOptions,
|
||||||
o.recordCanvas,
|
o.recordCanvas,
|
||||||
o.slimDOMOptions,
|
o.slimDOMOptions,
|
||||||
|
o.iframeManager,
|
||||||
);
|
);
|
||||||
const mousemoveHandler = initMoveObserver(o.mousemoveCb, o.sampling);
|
const mousemoveHandler = initMoveObserver(o.mousemoveCb, o.sampling, o.doc);
|
||||||
const mouseInteractionHandler = initMouseInteractionObserver(
|
const mouseInteractionHandler = initMouseInteractionObserver(
|
||||||
o.mouseInteractionCb,
|
o.mouseInteractionCb,
|
||||||
|
o.doc,
|
||||||
o.blockClass,
|
o.blockClass,
|
||||||
o.sampling,
|
o.sampling,
|
||||||
);
|
);
|
||||||
const scrollHandler = initScrollObserver(
|
const scrollHandler = initScrollObserver(
|
||||||
o.scrollCb,
|
o.scrollCb,
|
||||||
|
o.doc,
|
||||||
o.blockClass,
|
o.blockClass,
|
||||||
o.sampling,
|
o.sampling,
|
||||||
);
|
);
|
||||||
const viewportResizeHandler = initViewportResizeObserver(o.viewportResizeCb);
|
const viewportResizeHandler = initViewportResizeObserver(o.viewportResizeCb);
|
||||||
const inputHandler = initInputObserver(
|
const inputHandler = initInputObserver(
|
||||||
o.inputCb,
|
o.inputCb,
|
||||||
|
o.doc,
|
||||||
o.blockClass,
|
o.blockClass,
|
||||||
o.ignoreClass,
|
o.ignoreClass,
|
||||||
o.maskInputOptions,
|
o.maskInputOptions,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
MouseInteractions,
|
MouseInteractions,
|
||||||
playerConfig,
|
playerConfig,
|
||||||
playerMetaData,
|
playerMetaData,
|
||||||
viewportResizeDimention,
|
viewportResizeDimension,
|
||||||
missingNodeMap,
|
missingNodeMap,
|
||||||
addedNodeMutation,
|
addedNodeMutation,
|
||||||
missingNode,
|
missingNode,
|
||||||
@@ -37,6 +37,9 @@ import {
|
|||||||
TreeIndex,
|
TreeIndex,
|
||||||
queueToResolveTrees,
|
queueToResolveTrees,
|
||||||
iterateResolveTree,
|
iterateResolveTree,
|
||||||
|
AppendedIframe,
|
||||||
|
isIframeINode,
|
||||||
|
getBaseDimension,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import getInjectStyleRules from './styles/inject-style';
|
import getInjectStyleRules from './styles/inject-style';
|
||||||
import './styles/style.css';
|
import './styles/style.css';
|
||||||
@@ -49,6 +52,7 @@ const SKIP_TIME_INTERVAL = 5 * 1000;
|
|||||||
const mitt = (mittProxy as any).default || mittProxy;
|
const mitt = (mittProxy as any).default || mittProxy;
|
||||||
|
|
||||||
const REPLAY_CONSOLE_PREFIX = '[replayer]';
|
const REPLAY_CONSOLE_PREFIX = '[replayer]';
|
||||||
|
const SCROLL_ATTRIBUTE_NAME = '__rrweb_scroll__';
|
||||||
|
|
||||||
const defaultMouseTailConfig = {
|
const defaultMouseTailConfig = {
|
||||||
duration: 500,
|
duration: 500,
|
||||||
@@ -111,6 +115,8 @@ export class Replayer {
|
|||||||
|
|
||||||
private imageMap: Map<eventWithTime, HTMLImageElement> = new Map();
|
private imageMap: Map<eventWithTime, HTMLImageElement> = new Map();
|
||||||
|
|
||||||
|
private newDocumentQueue: addedNodeMutation[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
events: Array<eventWithTime | string>,
|
events: Array<eventWithTime | string>,
|
||||||
config?: Partial<playerConfig>,
|
config?: Partial<playerConfig>,
|
||||||
@@ -233,6 +239,10 @@ export class Replayer {
|
|||||||
}
|
}
|
||||||
if (firstFullsnapshot) {
|
if (firstFullsnapshot) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
// when something has been played, there is no need to rebuild poster
|
||||||
|
if (this.timer.timeOffset > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.rebuildFullSnapshot(
|
this.rebuildFullSnapshot(
|
||||||
firstFullsnapshot as fullSnapshotEvent & { timestamp: number },
|
firstFullsnapshot as fullSnapshotEvent & { timestamp: number },
|
||||||
);
|
);
|
||||||
@@ -406,7 +416,7 @@ export class Replayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleResize(dimension: viewportResizeDimention) {
|
private handleResize(dimension: viewportResizeDimension) {
|
||||||
this.iframe.style.display = 'inherit';
|
this.iframe.style.display = 'inherit';
|
||||||
for (const el of [this.mouseTail, this.iframe]) {
|
for (const el of [this.mouseTail, this.iframe]) {
|
||||||
if (!el) {
|
if (!el) {
|
||||||
@@ -534,11 +544,44 @@ export class Replayer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.legacy_missingNodeRetryMap = {};
|
this.legacy_missingNodeRetryMap = {};
|
||||||
|
const collected: AppendedIframe[] = [];
|
||||||
mirror.map = rebuild(event.data.node, {
|
mirror.map = rebuild(event.data.node, {
|
||||||
doc: this.iframe.contentDocument,
|
doc: this.iframe.contentDocument,
|
||||||
|
afterAppend: (builtNode) => {
|
||||||
|
this.collectIframeAndAttachDocument(collected, builtNode);
|
||||||
|
},
|
||||||
})[1];
|
})[1];
|
||||||
const styleEl = document.createElement('style');
|
for (const { mutationInQueue, builtNode } of collected) {
|
||||||
|
this.attachDocumentToIframe(mutationInQueue, builtNode);
|
||||||
|
this.newDocumentQueue = this.newDocumentQueue.filter(
|
||||||
|
(m) => m !== mutationInQueue,
|
||||||
|
);
|
||||||
|
if (builtNode.contentDocument) {
|
||||||
|
const { documentElement, head } = builtNode.contentDocument;
|
||||||
|
this.insertStyleRules(documentElement, head);
|
||||||
|
}
|
||||||
|
}
|
||||||
const { documentElement, head } = this.iframe.contentDocument;
|
const { documentElement, head } = this.iframe.contentDocument;
|
||||||
|
this.insertStyleRules(documentElement, head);
|
||||||
|
if (!this.service.state.matches('playing')) {
|
||||||
|
this.iframe.contentDocument
|
||||||
|
.getElementsByTagName('html')[0]
|
||||||
|
.classList.add('rrweb-paused');
|
||||||
|
}
|
||||||
|
this.emitter.emit(ReplayerEvents.FullsnapshotRebuilded, event);
|
||||||
|
if (!isSync) {
|
||||||
|
this.waitForStylesheetLoad();
|
||||||
|
}
|
||||||
|
if (this.config.UNSAFE_replayCanvas) {
|
||||||
|
this.preloadAllImages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private insertStyleRules(
|
||||||
|
documentElement: HTMLElement,
|
||||||
|
head: HTMLHeadElement,
|
||||||
|
) {
|
||||||
|
const styleEl = document.createElement('style');
|
||||||
documentElement!.insertBefore(styleEl, head);
|
documentElement!.insertBefore(styleEl, head);
|
||||||
const injectStylesRules = getInjectStyleRules(
|
const injectStylesRules = getInjectStyleRules(
|
||||||
this.config.blockClass,
|
this.config.blockClass,
|
||||||
@@ -548,20 +591,48 @@ export class Replayer {
|
|||||||
'html.rrweb-paused * { animation-play-state: paused !important; }',
|
'html.rrweb-paused * { animation-play-state: paused !important; }',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!this.service.state.matches('playing')) {
|
|
||||||
this.iframe.contentDocument
|
|
||||||
.getElementsByTagName('html')[0]
|
|
||||||
.classList.add('rrweb-paused');
|
|
||||||
}
|
|
||||||
for (let idx = 0; idx < injectStylesRules.length; idx++) {
|
for (let idx = 0; idx < injectStylesRules.length; idx++) {
|
||||||
(styleEl.sheet! as CSSStyleSheet).insertRule(injectStylesRules[idx], idx);
|
(styleEl.sheet! as CSSStyleSheet).insertRule(injectStylesRules[idx], idx);
|
||||||
}
|
}
|
||||||
this.emitter.emit(ReplayerEvents.FullsnapshotRebuilded, event);
|
}
|
||||||
if (!isSync) {
|
|
||||||
this.waitForStylesheetLoad();
|
private attachDocumentToIframe(
|
||||||
|
mutation: addedNodeMutation,
|
||||||
|
iframeEl: HTMLIFrameElement,
|
||||||
|
) {
|
||||||
|
const collected: AppendedIframe[] = [];
|
||||||
|
buildNodeWithSN(mutation.node, {
|
||||||
|
doc: iframeEl.contentDocument!,
|
||||||
|
map: mirror.map,
|
||||||
|
hackCss: true,
|
||||||
|
skipChild: false,
|
||||||
|
afterAppend: (builtNode) => {
|
||||||
|
this.collectIframeAndAttachDocument(collected, builtNode);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
for (const { mutationInQueue, builtNode } of collected) {
|
||||||
|
this.attachDocumentToIframe(mutationInQueue, builtNode);
|
||||||
|
this.newDocumentQueue = this.newDocumentQueue.filter(
|
||||||
|
(m) => m !== mutationInQueue,
|
||||||
|
);
|
||||||
|
if (builtNode.contentDocument) {
|
||||||
|
const { documentElement, head } = builtNode.contentDocument;
|
||||||
|
this.insertStyleRules(documentElement, head);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (this.config.UNSAFE_replayCanvas) {
|
}
|
||||||
this.preloadAllImages();
|
|
||||||
|
private collectIframeAndAttachDocument(
|
||||||
|
collected: AppendedIframe[],
|
||||||
|
builtNode: INode,
|
||||||
|
) {
|
||||||
|
if (isIframeINode(builtNode)) {
|
||||||
|
const mutationInQueue = this.newDocumentQueue.find(
|
||||||
|
(m) => m.parentId === builtNode.__sn.id,
|
||||||
|
);
|
||||||
|
if (mutationInQueue) {
|
||||||
|
collected.push({ mutationInQueue, builtNode });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1021,6 +1092,10 @@ export class Replayer {
|
|||||||
}
|
}
|
||||||
let parent = mirror.getNode(mutation.parentId);
|
let parent = mirror.getNode(mutation.parentId);
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
|
if (mutation.node.type === NodeType.Document) {
|
||||||
|
// is newly added document, maybe the document node of an iframe
|
||||||
|
return this.newDocumentQueue.push(mutation);
|
||||||
|
}
|
||||||
return queue.push(mutation);
|
return queue.push(mutation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1059,12 +1134,23 @@ export class Replayer {
|
|||||||
return queue.push(mutation);
|
return queue.push(mutation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mutation.node.rootId && !mirror.getNode(mutation.node.rootId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetDoc = mutation.node.rootId
|
||||||
|
? mirror.getNode(mutation.node.rootId)
|
||||||
|
: this.iframe.contentDocument;
|
||||||
|
if (isIframeINode(parent)) {
|
||||||
|
this.attachDocumentToIframe(mutation, parent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const target = buildNodeWithSN(mutation.node, {
|
const target = buildNodeWithSN(mutation.node, {
|
||||||
doc: this.iframe.contentDocument,
|
doc: targetDoc as Document,
|
||||||
map: mirror.map,
|
map: mirror.map,
|
||||||
skipChild: true,
|
skipChild: true,
|
||||||
hackCss: true,
|
hackCss: true,
|
||||||
}) as Node;
|
}) as INode;
|
||||||
|
|
||||||
// legacy data, we should not have -1 siblings any more
|
// legacy data, we should not have -1 siblings any more
|
||||||
if (mutation.previousId === -1 || mutation.nextId === -1) {
|
if (mutation.previousId === -1 || mutation.nextId === -1) {
|
||||||
@@ -1087,6 +1173,22 @@ export class Replayer {
|
|||||||
parent.appendChild(target);
|
parent.appendChild(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isIframeINode(target)) {
|
||||||
|
const mutationInQueue = this.newDocumentQueue.find(
|
||||||
|
(m) => m.parentId === target.__sn.id,
|
||||||
|
);
|
||||||
|
if (mutationInQueue) {
|
||||||
|
this.attachDocumentToIframe(mutationInQueue, target);
|
||||||
|
this.newDocumentQueue = this.newDocumentQueue.filter(
|
||||||
|
(m) => m !== mutationInQueue,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (target.contentDocument) {
|
||||||
|
const { documentElement, head } = target.contentDocument;
|
||||||
|
this.insertStyleRules(documentElement, head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mutation.previousId || mutation.nextId) {
|
if (mutation.previousId || mutation.nextId) {
|
||||||
this.legacy_resolveMissingNode(
|
this.legacy_resolveMissingNode(
|
||||||
legacy_missingNodeMap,
|
legacy_missingNodeMap,
|
||||||
@@ -1228,7 +1330,7 @@ export class Replayer {
|
|||||||
* generate a console log replayer which implement the interface ReplayLogger
|
* generate a console log replayer which implement the interface ReplayLogger
|
||||||
*/
|
*/
|
||||||
private getConsoleLogger(): ReplayLogger {
|
private getConsoleLogger(): ReplayLogger {
|
||||||
const rrwebOriginal = '__rrweb_original__';
|
const rrwebOriginal = SCROLL_ATTRIBUTE_NAME;
|
||||||
const replayLogger: ReplayLogger = {};
|
const replayLogger: ReplayLogger = {};
|
||||||
for (const level of this.config.logConfig.level!)
|
for (const level of this.config.logConfig.level!)
|
||||||
if (level === 'trace')
|
if (level === 'trace')
|
||||||
@@ -1284,14 +1386,18 @@ export class Replayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private moveAndHover(d: incrementalData, x: number, y: number, id: number) {
|
private moveAndHover(d: incrementalData, x: number, y: number, id: number) {
|
||||||
this.mouse.style.left = `${x}px`;
|
|
||||||
this.mouse.style.top = `${y}px`;
|
|
||||||
this.drawMouseTail({ x, y });
|
|
||||||
|
|
||||||
const target = mirror.getNode(id);
|
const target = mirror.getNode(id);
|
||||||
if (!target) {
|
if (!target) {
|
||||||
return this.debugNodeNotFound(d, id);
|
return this.debugNodeNotFound(d, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const base = getBaseDimension(target);
|
||||||
|
const _x = x + base.x;
|
||||||
|
const _y = y + base.y;
|
||||||
|
|
||||||
|
this.mouse.style.left = `${_x}px`;
|
||||||
|
this.mouse.style.top = `${_y}px`;
|
||||||
|
this.drawMouseTail({ x: _x, y: _y });
|
||||||
this.hoverElements((target as Node) as Element);
|
this.hoverElements((target as Node) as Element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const rules: (blockClass: string) => string[] = (blockClass: string) => [
|
const rules: (blockClass: string) => string[] = (blockClass: string) => [
|
||||||
`iframe, .${blockClass} { background: #ccc }`,
|
`.${blockClass} { background: #ccc }`,
|
||||||
'noscript { display: none !important; }',
|
'noscript { display: none !important; }',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
14
src/types.ts
14
src/types.ts
@@ -7,6 +7,7 @@ import {
|
|||||||
} from 'rrweb-snapshot';
|
} from 'rrweb-snapshot';
|
||||||
import { PackFn, UnpackFn } from './packer/base';
|
import { PackFn, UnpackFn } from './packer/base';
|
||||||
import { FontFaceDescriptors } from 'css-font-loading-module';
|
import { FontFaceDescriptors } from 'css-font-loading-module';
|
||||||
|
import { IframeManager } from './record/iframe-manager';
|
||||||
|
|
||||||
export enum EventType {
|
export enum EventType {
|
||||||
DomContentLoaded,
|
DomContentLoaded,
|
||||||
@@ -101,7 +102,7 @@ export type scrollData = {
|
|||||||
|
|
||||||
export type viewportResizeData = {
|
export type viewportResizeData = {
|
||||||
source: IncrementalSource.ViewportResize;
|
source: IncrementalSource.ViewportResize;
|
||||||
} & viewportResizeDimention;
|
} & viewportResizeDimension;
|
||||||
|
|
||||||
export type inputData = {
|
export type inputData = {
|
||||||
source: IncrementalSource.Input;
|
source: IncrementalSource.Input;
|
||||||
@@ -224,6 +225,8 @@ export type observerParam = {
|
|||||||
recordCanvas: boolean;
|
recordCanvas: boolean;
|
||||||
collectFonts: boolean;
|
collectFonts: boolean;
|
||||||
slimDOMOptions: SlimDOMOptions;
|
slimDOMOptions: SlimDOMOptions;
|
||||||
|
doc: Document;
|
||||||
|
iframeManager: IframeManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type hooksParam = {
|
export type hooksParam = {
|
||||||
@@ -430,12 +433,12 @@ export type fontCallback = (p: fontParam) => void;
|
|||||||
|
|
||||||
export type logCallback = (p: LogParam) => void;
|
export type logCallback = (p: LogParam) => void;
|
||||||
|
|
||||||
export type viewportResizeDimention = {
|
export type viewportResizeDimension = {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type viewportResizeCallback = (d: viewportResizeDimention) => void;
|
export type viewportResizeCallback = (d: viewportResizeDimension) => void;
|
||||||
|
|
||||||
export type inputValue = {
|
export type inputValue = {
|
||||||
text: string;
|
text: string;
|
||||||
@@ -456,6 +459,11 @@ export type mediaInteractionParam = {
|
|||||||
|
|
||||||
export type mediaInteractionCallback = (p: mediaInteractionParam) => void;
|
export type mediaInteractionCallback = (p: mediaInteractionParam) => void;
|
||||||
|
|
||||||
|
export type DocumentDimension = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type Mirror = {
|
export type Mirror = {
|
||||||
map: idNodeMap;
|
map: idNodeMap;
|
||||||
getId: (n: INode) => number;
|
getId: (n: INode) => number;
|
||||||
|
|||||||
41
src/utils.ts
41
src/utils.ts
@@ -14,8 +14,14 @@ import {
|
|||||||
mutationData,
|
mutationData,
|
||||||
scrollData,
|
scrollData,
|
||||||
inputData,
|
inputData,
|
||||||
|
DocumentDimension,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { INode, IGNORED_NODE } from 'rrweb-snapshot';
|
import {
|
||||||
|
INode,
|
||||||
|
IGNORED_NODE,
|
||||||
|
serializedNodeWithId,
|
||||||
|
NodeType,
|
||||||
|
} from 'rrweb-snapshot';
|
||||||
|
|
||||||
export function on(
|
export function on(
|
||||||
type: string,
|
type: string,
|
||||||
@@ -527,3 +533,36 @@ export function iterateResolveTree(
|
|||||||
iterateResolveTree(tree.children[i], cb);
|
iterateResolveTree(tree.children[i], cb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HTMLIFrameINode = HTMLIFrameElement & {
|
||||||
|
__sn: serializedNodeWithId;
|
||||||
|
};
|
||||||
|
export type AppendedIframe = {
|
||||||
|
mutationInQueue: addedNodeMutation;
|
||||||
|
builtNode: HTMLIFrameINode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isIframeINode(node: INode): node is HTMLIFrameINode {
|
||||||
|
// node can be document fragment when using the virtual parent feature
|
||||||
|
if (!node.__sn) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return node.__sn.type === NodeType.Element && node.__sn.tagName === 'iframe';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBaseDimension(node: Node): DocumentDimension {
|
||||||
|
const frameElement = node.ownerDocument?.defaultView?.frameElement;
|
||||||
|
if (!frameElement) {
|
||||||
|
return {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const frameDimension = frameElement.getBoundingClientRect();
|
||||||
|
const frameBaseDimension = getBaseDimension(frameElement);
|
||||||
|
return {
|
||||||
|
x: frameDimension.x + frameBaseDimension.x,
|
||||||
|
y: frameDimension.y + frameBaseDimension.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1727,6 +1727,708 @@ exports[`frozen 1`] = `
|
|||||||
]"
|
]"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`iframe 1`] = `
|
||||||
|
"[
|
||||||
|
{
|
||||||
|
\\"type\\": 0,
|
||||||
|
\\"data\\": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 1,
|
||||||
|
\\"data\\": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 4,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"href\\": \\"about:blank\\",
|
||||||
|
\\"width\\": 1920,
|
||||||
|
\\"height\\": 1080
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 0,
|
||||||
|
\\"adds\\": [
|
||||||
|
{
|
||||||
|
\\"parentId\\": 19,
|
||||||
|
\\"nextId\\": null,
|
||||||
|
\\"node\\": {
|
||||||
|
\\"type\\": 0,
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"html\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"head\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"rootId\\": 20,
|
||||||
|
\\"id\\": 22
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"body\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"rootId\\": 20,
|
||||||
|
\\"id\\": 23
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"rootId\\": 20,
|
||||||
|
\\"id\\": 21
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"removes\\": [],
|
||||||
|
\\"texts\\": [],
|
||||||
|
\\"attributes\\": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"node\\": {
|
||||||
|
\\"type\\": 0,
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 1,
|
||||||
|
\\"name\\": \\"html\\",
|
||||||
|
\\"publicId\\": \\"\\",
|
||||||
|
\\"systemId\\": \\"\\",
|
||||||
|
\\"id\\": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"html\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"lang\\": \\"en\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"head\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"id\\": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"meta\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"charset\\": \\"UTF-8\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"id\\": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"id\\": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"meta\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"name\\": \\"viewport\\",
|
||||||
|
\\"content\\": \\"width=device-width, initial-scale=1.0\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"id\\": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"id\\": 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"title\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"Main\\",
|
||||||
|
\\"id\\": 11
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"id\\": 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"style\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n iframe {\\\\n width: 500px;\\\\n height: 500px;\\\\n }\\\\n \\",
|
||||||
|
\\"isStyle\\": true,
|
||||||
|
\\"id\\": 14
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 13
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"id\\": 15
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"id\\": 16
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"body\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"id\\": 18
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"iframe\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"id\\": \\"one\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"id\\": 19
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\\\n \\",
|
||||||
|
\\"id\\": 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"script\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
|
||||||
|
\\"id\\": 26
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\\\n \\\\n \\",
|
||||||
|
\\"id\\": 27
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"script\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
|
||||||
|
\\"id\\": 29
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 28
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n\\\\n\\",
|
||||||
|
\\"id\\": 30
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 17
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 1
|
||||||
|
},
|
||||||
|
\\"initialOffset\\": {
|
||||||
|
\\"left\\": 0,
|
||||||
|
\\"top\\": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 0,
|
||||||
|
\\"texts\\": [],
|
||||||
|
\\"attributes\\": [],
|
||||||
|
\\"removes\\": [],
|
||||||
|
\\"adds\\": [
|
||||||
|
{
|
||||||
|
\\"parentId\\": 17,
|
||||||
|
\\"nextId\\": null,
|
||||||
|
\\"node\\": {
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"iframe\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"id\\": \\"two\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"id\\": 31
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 0,
|
||||||
|
\\"adds\\": [
|
||||||
|
{
|
||||||
|
\\"parentId\\": 47,
|
||||||
|
\\"nextId\\": null,
|
||||||
|
\\"node\\": {
|
||||||
|
\\"type\\": 0,
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"html\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"head\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"rootId\\": 48,
|
||||||
|
\\"id\\": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"body\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"rootId\\": 48,
|
||||||
|
\\"id\\": 51
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"rootId\\": 48,
|
||||||
|
\\"id\\": 49
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 48
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"removes\\": [],
|
||||||
|
\\"texts\\": [],
|
||||||
|
\\"attributes\\": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 0,
|
||||||
|
\\"adds\\": [
|
||||||
|
{
|
||||||
|
\\"parentId\\": 53,
|
||||||
|
\\"nextId\\": null,
|
||||||
|
\\"node\\": {
|
||||||
|
\\"type\\": 0,
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 1,
|
||||||
|
\\"name\\": \\"html\\",
|
||||||
|
\\"publicId\\": \\"\\",
|
||||||
|
\\"systemId\\": \\"\\",
|
||||||
|
\\"rootId\\": 54,
|
||||||
|
\\"id\\": 55
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"html\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"lang\\": \\"en\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"head\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"rootId\\": 54,
|
||||||
|
\\"id\\": 58
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"meta\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"charset\\": \\"UTF-8\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"rootId\\": 54,
|
||||||
|
\\"id\\": 59
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"rootId\\": 54,
|
||||||
|
\\"id\\": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"meta\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"name\\": \\"viewport\\",
|
||||||
|
\\"content\\": \\"width=device-width, initial-scale=1.0\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"rootId\\": 54,
|
||||||
|
\\"id\\": 61
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"rootId\\": 54,
|
||||||
|
\\"id\\": 62
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"title\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"Frame 2\\",
|
||||||
|
\\"rootId\\": 54,
|
||||||
|
\\"id\\": 64
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"rootId\\": 54,
|
||||||
|
\\"id\\": 63
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"rootId\\": 54,
|
||||||
|
\\"id\\": 65
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"rootId\\": 54,
|
||||||
|
\\"id\\": 57
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"rootId\\": 54,
|
||||||
|
\\"id\\": 66
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"body\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n frame 2\\\\n \\\\n \\",
|
||||||
|
\\"rootId\\": 54,
|
||||||
|
\\"id\\": 68
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"script\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
|
||||||
|
\\"rootId\\": 54,
|
||||||
|
\\"id\\": 70
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"rootId\\": 54,
|
||||||
|
\\"id\\": 69
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n\\\\n\\",
|
||||||
|
\\"rootId\\": 54,
|
||||||
|
\\"id\\": 71
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"rootId\\": 54,
|
||||||
|
\\"id\\": 67
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"rootId\\": 54,
|
||||||
|
\\"id\\": 56
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 54
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"removes\\": [],
|
||||||
|
\\"texts\\": [],
|
||||||
|
\\"attributes\\": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 0,
|
||||||
|
\\"adds\\": [
|
||||||
|
{
|
||||||
|
\\"parentId\\": 31,
|
||||||
|
\\"nextId\\": null,
|
||||||
|
\\"node\\": {
|
||||||
|
\\"type\\": 0,
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 1,
|
||||||
|
\\"name\\": \\"html\\",
|
||||||
|
\\"publicId\\": \\"\\",
|
||||||
|
\\"systemId\\": \\"\\",
|
||||||
|
\\"rootId\\": 32,
|
||||||
|
\\"id\\": 33
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"html\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"lang\\": \\"en\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"head\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"rootId\\": 32,
|
||||||
|
\\"id\\": 36
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"meta\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"charset\\": \\"UTF-8\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"rootId\\": 32,
|
||||||
|
\\"id\\": 37
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"rootId\\": 32,
|
||||||
|
\\"id\\": 38
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"meta\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"name\\": \\"viewport\\",
|
||||||
|
\\"content\\": \\"width=device-width, initial-scale=1.0\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"rootId\\": 32,
|
||||||
|
\\"id\\": 39
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"rootId\\": 32,
|
||||||
|
\\"id\\": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"title\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"Frame 1\\",
|
||||||
|
\\"rootId\\": 32,
|
||||||
|
\\"id\\": 42
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"rootId\\": 32,
|
||||||
|
\\"id\\": 41
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"rootId\\": 32,
|
||||||
|
\\"id\\": 43
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"rootId\\": 32,
|
||||||
|
\\"id\\": 35
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"rootId\\": 32,
|
||||||
|
\\"id\\": 44
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"body\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n frame 1\\\\n \\",
|
||||||
|
\\"rootId\\": 32,
|
||||||
|
\\"id\\": 46
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"iframe\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"id\\": \\"three\\",
|
||||||
|
\\"frameborder\\": \\"0\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"rootId\\": 32,
|
||||||
|
\\"id\\": 47
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\",
|
||||||
|
\\"rootId\\": 32,
|
||||||
|
\\"id\\": 52
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"iframe\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"id\\": \\"four\\",
|
||||||
|
\\"frameborder\\": \\"0\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"rootId\\": 32,
|
||||||
|
\\"id\\": 53
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"textContent\\": \\"\\\\n \\\\n\\\\n\\",
|
||||||
|
\\"rootId\\": 32,
|
||||||
|
\\"id\\": 72
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"rootId\\": 32,
|
||||||
|
\\"id\\": 45
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"rootId\\": 32,
|
||||||
|
\\"id\\": 34
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"removes\\": [],
|
||||||
|
\\"texts\\": [],
|
||||||
|
\\"attributes\\": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 0,
|
||||||
|
\\"adds\\": [
|
||||||
|
{
|
||||||
|
\\"parentId\\": 73,
|
||||||
|
\\"nextId\\": null,
|
||||||
|
\\"node\\": {
|
||||||
|
\\"type\\": 0,
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"html\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"head\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"rootId\\": 74,
|
||||||
|
\\"id\\": 76
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"body\\",
|
||||||
|
\\"attributes\\": {},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"rootId\\": 74,
|
||||||
|
\\"id\\": 77
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"rootId\\": 74,
|
||||||
|
\\"id\\": 75
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"id\\": 74
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
\\"removes\\": [],
|
||||||
|
\\"texts\\": [],
|
||||||
|
\\"attributes\\": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
\\"type\\": 3,
|
||||||
|
\\"data\\": {
|
||||||
|
\\"source\\": 0,
|
||||||
|
\\"texts\\": [],
|
||||||
|
\\"attributes\\": [],
|
||||||
|
\\"removes\\": [],
|
||||||
|
\\"adds\\": [
|
||||||
|
{
|
||||||
|
\\"parentId\\": 67,
|
||||||
|
\\"nextId\\": null,
|
||||||
|
\\"node\\": {
|
||||||
|
\\"type\\": 2,
|
||||||
|
\\"tagName\\": \\"iframe\\",
|
||||||
|
\\"attributes\\": {
|
||||||
|
\\"id\\": \\"five\\"
|
||||||
|
},
|
||||||
|
\\"childNodes\\": [],
|
||||||
|
\\"rootId\\": 54,
|
||||||
|
\\"id\\": 73
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`ignore 1`] = `
|
exports[`ignore 1`] = `
|
||||||
"[
|
"[
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import {
|
import { EventType, eventWithTime, IncrementalSource } from '../../src/types';
|
||||||
EventType,
|
|
||||||
eventWithTime,
|
|
||||||
IncrementalSource
|
|
||||||
} from '../../src/types';
|
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const events: eventWithTime[] = [
|
const events: eventWithTime[] = [
|
||||||
@@ -27,68 +23,124 @@ const events: eventWithTime[] = [
|
|||||||
},
|
},
|
||||||
// full snapshot:
|
// full snapshot:
|
||||||
{
|
{
|
||||||
"data": {
|
data: {
|
||||||
"node": {
|
node: {
|
||||||
"id": 1, "type": 0, "childNodes": [{ "id": 2, "name": "html", "type": 1, "publicId": "", "systemId": "" }, {
|
id: 1,
|
||||||
"id": 3, "type": 2, "tagName": "html", "attributes": { "lang": "en" }, "childNodes": [{
|
type: 0,
|
||||||
"id": 4, "type": 2, "tagName": "head", "attributes": {}, "childNodes": [
|
childNodes: [
|
||||||
|
{ id: 2, name: 'html', type: 1, publicId: '', systemId: '' },
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
type: 2,
|
||||||
|
tagName: 'html',
|
||||||
|
attributes: { lang: 'en' },
|
||||||
|
childNodes: [
|
||||||
{
|
{
|
||||||
"id": 101, "type": 2, "tagName": "style", "attributes": { "data-jss": "", "data-meta": "sk, Unthemed, Static" }, "childNodes": [{ "id": 102, "type": 3, "isStyle": true, "textContent": "\n.c01x {\n opacity: 1;\n transform: translateX(0);\n}\n" }]
|
id: 4,
|
||||||
|
type: 2,
|
||||||
|
tagName: 'head',
|
||||||
|
attributes: {},
|
||||||
|
childNodes: [
|
||||||
|
{
|
||||||
|
id: 101,
|
||||||
|
type: 2,
|
||||||
|
tagName: 'style',
|
||||||
|
attributes: {
|
||||||
|
'data-jss': '',
|
||||||
|
'data-meta': 'sk, Unthemed, Static',
|
||||||
|
},
|
||||||
|
childNodes: [
|
||||||
|
{
|
||||||
|
id: 102,
|
||||||
|
type: 3,
|
||||||
|
isStyle: true,
|
||||||
|
textContent:
|
||||||
|
'\n.c01x {\n opacity: 1;\n transform: translateX(0);\n}\n',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 105,
|
||||||
|
type: 2,
|
||||||
|
tagName: 'style',
|
||||||
|
attributes: {
|
||||||
|
_cssText:
|
||||||
|
'.css-1uxxxx3 { position: fixed; top: 0px; right: 0px; left: 4rem; z-index: 15; flex-shrink: 0; height: 0.25rem; overflow: hidden; background-color: rgb(17, 171, 209); }.css-1c9xxxx { height: 0.25rem; background-color: rgb(190, 232, 242); opacity: 0; transition: opacity 0.5s ease 0s; }.css-lsxxx { padding-left: 4rem; }',
|
||||||
|
'data-emotion': 'css',
|
||||||
|
},
|
||||||
|
childNodes: [
|
||||||
|
{ id: 106, type: 3, isStyle: true, textContent: '' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 105, "type": 2, "tagName": "style", "attributes":
|
id: 107,
|
||||||
{ "_cssText": ".css-1uxxxx3 { position: fixed; top: 0px; right: 0px; left: 4rem; z-index: 15; flex-shrink: 0; height: 0.25rem; overflow: hidden; background-color: rgb(17, 171, 209); }.css-1c9xxxx { height: 0.25rem; background-color: rgb(190, 232, 242); opacity: 0; transition: opacity 0.5s ease 0s; }.css-lsxxx { padding-left: 4rem; }", "data-emotion": "css" }, "childNodes": [{ "id": 106, "type": 3, "isStyle": true, "textContent": "" }]
|
type: 2,
|
||||||
}]
|
tagName: 'body',
|
||||||
}, {
|
attributes: {},
|
||||||
"id": 107, "type": 2, "tagName": "body", "attributes": {}, "childNodes": []
|
childNodes: [],
|
||||||
}]
|
},
|
||||||
}]
|
],
|
||||||
}, "initialOffset": { "top": 0, "left": 0 }
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
initialOffset: { top: 0, left: 0 },
|
||||||
},
|
},
|
||||||
"type": EventType.FullSnapshot,
|
type: EventType.FullSnapshot,
|
||||||
"timestamp": now + 100
|
timestamp: now + 100,
|
||||||
},
|
},
|
||||||
// mutation that adds stylesheet
|
// mutation that adds stylesheet
|
||||||
{
|
{
|
||||||
"data": {
|
data: {
|
||||||
"adds": [
|
adds: [
|
||||||
{
|
{
|
||||||
"node": {
|
node: {
|
||||||
"id": 255, "type": 2, "tagName": "style", "attributes": { "data-jss": "", "data-meta": "Col, Themed, Dynamic" }, "childNodes": []
|
id: 255,
|
||||||
|
type: 2,
|
||||||
|
tagName: 'style',
|
||||||
|
attributes: { 'data-jss': '', 'data-meta': 'Col, Themed, Dynamic' },
|
||||||
|
childNodes: [],
|
||||||
},
|
},
|
||||||
"nextId": 101,
|
nextId: 101,
|
||||||
"parentId": 4
|
parentId: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"node": {
|
node: {
|
||||||
"id": 256, "type": 3, "isStyle": true, "textContent": "\n.c011xx {\n padding: 1.3125rem;\n flex: none;\n width: 100%;\n}\n"
|
id: 256,
|
||||||
|
type: 3,
|
||||||
|
isStyle: true,
|
||||||
|
textContent:
|
||||||
|
'\n.c011xx {\n padding: 1.3125rem;\n flex: none;\n width: 100%;\n}\n',
|
||||||
},
|
},
|
||||||
"nextId": null,
|
nextId: null,
|
||||||
"parentId": 255
|
parentId: 255,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"texts": [],
|
texts: [],
|
||||||
"source": IncrementalSource.Mutation,
|
source: IncrementalSource.Mutation,
|
||||||
"removes": [],
|
removes: [],
|
||||||
"attributes": []
|
attributes: [],
|
||||||
},
|
},
|
||||||
"type": EventType.IncrementalSnapshot,
|
type: EventType.IncrementalSnapshot,
|
||||||
"timestamp": now + 500
|
timestamp: now + 500,
|
||||||
},
|
},
|
||||||
// adds StyleSheetRule
|
// adds StyleSheetRule
|
||||||
{
|
{
|
||||||
"data": {
|
data: {
|
||||||
"id": 105, "adds": [
|
id: 105,
|
||||||
|
adds: [
|
||||||
{
|
{
|
||||||
"rule": ".css-1fbxx79{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;min-width:60rem;min-height:100vh;}",
|
rule:
|
||||||
"index": 2
|
'.css-1fbxx79{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;min-width:60rem;min-height:100vh;}',
|
||||||
}
|
index: 2,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"source": IncrementalSource.StyleSheetRule
|
source: IncrementalSource.StyleSheetRule,
|
||||||
},
|
},
|
||||||
"type": EventType.IncrementalSnapshot,
|
type: EventType.IncrementalSnapshot,
|
||||||
"timestamp": now + 1000
|
timestamp: now + 1000,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default events;
|
export default events;
|
||||||
|
|||||||
13
test/html/frame1.html
Normal file
13
test/html/frame1.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Frame 1</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
frame 1
|
||||||
|
<iframe id="three" frameborder="0"></iframe>
|
||||||
|
<iframe id="four" src="./frame2.html" frameborder="0"></iframe>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
18
test/html/frame2.html
Normal file
18
test/html/frame2.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Frame 2</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
frame 2
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
const iframe5 = document.createElement('iframe');
|
||||||
|
iframe5.id = 'five';
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.appendChild(iframe5);
|
||||||
|
}, 10);
|
||||||
|
</script>
|
||||||
|
</html>
|
||||||
25
test/html/main.html
Normal file
25
test/html/main.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Main</title>
|
||||||
|
<style>
|
||||||
|
iframe {
|
||||||
|
width: 500px;
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<iframe id="one"></iframe>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
|
const iframe2 = document.createElement('iframe');
|
||||||
|
iframe2.id = 'two';
|
||||||
|
iframe2.src = './html/frame1.html';
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.appendChild(iframe2);
|
||||||
|
}, 10);
|
||||||
|
</script>
|
||||||
|
</html>
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
import * as http from 'http';
|
||||||
|
import * as url from 'url';
|
||||||
import * as puppeteer from 'puppeteer';
|
import * as puppeteer from 'puppeteer';
|
||||||
import { assertSnapshot, launchPuppeteer } from './utils';
|
import { assertSnapshot, launchPuppeteer } from './utils';
|
||||||
import { Suite } from 'mocha';
|
import { Suite } from 'mocha';
|
||||||
@@ -8,10 +10,48 @@ import { recordOptions, eventWithTime, EventType } from '../src/types';
|
|||||||
import { visitSnapshot, NodeType } from 'rrweb-snapshot';
|
import { visitSnapshot, NodeType } from 'rrweb-snapshot';
|
||||||
|
|
||||||
interface ISuite extends Suite {
|
interface ISuite extends Suite {
|
||||||
|
server: http.Server;
|
||||||
code: string;
|
code: string;
|
||||||
browser: puppeteer.Browser;
|
browser: puppeteer.Browser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IMimeType {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = () =>
|
||||||
|
new Promise<http.Server>((resolve) => {
|
||||||
|
const mimeType: IMimeType = {
|
||||||
|
'.html': 'text/html',
|
||||||
|
'.js': 'text/javascript',
|
||||||
|
'.css': 'text/css',
|
||||||
|
};
|
||||||
|
const s = http.createServer((req, res) => {
|
||||||
|
const parsedUrl = url.parse(req.url!);
|
||||||
|
const sanitizePath = path
|
||||||
|
.normalize(parsedUrl.pathname!)
|
||||||
|
.replace(/^(\.\.[\/\\])+/, '');
|
||||||
|
let pathname = path.join(__dirname, sanitizePath);
|
||||||
|
try {
|
||||||
|
const data = fs.readFileSync(pathname);
|
||||||
|
const ext = path.parse(pathname).ext;
|
||||||
|
res.setHeader('Content-type', mimeType[ext] || 'text/plain');
|
||||||
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||||
|
res.setHeader('Access-Control-Allow-Methods', 'GET');
|
||||||
|
res.setHeader('Access-Control-Allow-Headers', 'Content-type');
|
||||||
|
setTimeout(() => {
|
||||||
|
res.end(data);
|
||||||
|
// mock delay
|
||||||
|
}, 100);
|
||||||
|
} catch (error) {
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
s.listen(3030).on('listening', () => {
|
||||||
|
resolve(s);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('record integration tests', function (this: ISuite) {
|
describe('record integration tests', function (this: ISuite) {
|
||||||
this.timeout(10_000);
|
this.timeout(10_000);
|
||||||
|
|
||||||
@@ -44,6 +84,7 @@ describe('record integration tests', function (this: ISuite) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
|
this.server = await server();
|
||||||
this.browser = await launchPuppeteer();
|
this.browser = await launchPuppeteer();
|
||||||
|
|
||||||
const bundlePath = path.resolve(__dirname, '../dist/rrweb.min.js');
|
const bundlePath = path.resolve(__dirname, '../dist/rrweb.min.js');
|
||||||
@@ -52,6 +93,7 @@ describe('record integration tests', function (this: ISuite) {
|
|||||||
|
|
||||||
after(async () => {
|
after(async () => {
|
||||||
await this.browser.close();
|
await this.browser.close();
|
||||||
|
this.server.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can record form interactions', async () => {
|
it('can record form interactions', async () => {
|
||||||
@@ -367,4 +409,14 @@ describe('record integration tests', function (this: ISuite) {
|
|||||||
const snapshots = await page.evaluate('window.snapshots');
|
const snapshots = await page.evaluate('window.snapshots');
|
||||||
assertSnapshot(snapshots, __filename, 'log');
|
assertSnapshot(snapshots, __filename, 'log');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should nest record iframe', async () => {
|
||||||
|
const page: puppeteer.Page = await this.browser.newPage();
|
||||||
|
await page.goto(`http://localhost:3030/html`);
|
||||||
|
await page.setContent(getHtml.call(this, 'main.html'));
|
||||||
|
|
||||||
|
await page.waitFor(500);
|
||||||
|
const snapshots = await page.evaluate('window.snapshots');
|
||||||
|
assertSnapshot(snapshots, __filename, 'iframe');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Suite } from 'mocha';
|
|||||||
import {
|
import {
|
||||||
launchPuppeteer,
|
launchPuppeteer,
|
||||||
sampleEvents as events,
|
sampleEvents as events,
|
||||||
sampleStyleSheetRemoveEvents as stylesheetRemoveEvents
|
sampleStyleSheetRemoveEvents as stylesheetRemoveEvents,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import styleSheetRuleEvents from './events/style-sheet-rule-events';
|
import styleSheetRuleEvents from './events/style-sheet-rule-events';
|
||||||
|
|
||||||
@@ -127,17 +127,19 @@ describe('replayer', function (this: ISuite) {
|
|||||||
`);
|
`);
|
||||||
const currentTime = await this.page.evaluate(`
|
const currentTime = await this.page.evaluate(`
|
||||||
replayer.getCurrentTime();
|
replayer.getCurrentTime();
|
||||||
`)
|
`);
|
||||||
const currentState = await this.page.evaluate(`
|
const currentState = await this.page.evaluate(`
|
||||||
replayer['service']['state']['value'];
|
replayer['service']['state']['value'];
|
||||||
`)
|
`);
|
||||||
expect(actionLength).to.equal(0)
|
expect(actionLength).to.equal(0);
|
||||||
expect(currentTime).to.equal(2500);
|
expect(currentTime).to.equal(2500);
|
||||||
expect(currentState).to.equal('paused');
|
expect(currentState).to.equal('paused');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can fast forward past StyleSheetRule changes on virtual elements', async () => {
|
it('can fast forward past StyleSheetRule changes on virtual elements', async () => {
|
||||||
await this.page.evaluate(`events = ${JSON.stringify(styleSheetRuleEvents)}`);
|
await this.page.evaluate(
|
||||||
|
`events = ${JSON.stringify(styleSheetRuleEvents)}`,
|
||||||
|
);
|
||||||
const actionLength = await this.page.evaluate(`
|
const actionLength = await this.page.evaluate(`
|
||||||
const { Replayer } = rrweb;
|
const { Replayer } = rrweb;
|
||||||
const replayer = new Replayer(events);
|
const replayer = new Replayer(events);
|
||||||
@@ -145,12 +147,16 @@ describe('replayer', function (this: ISuite) {
|
|||||||
replayer['timer']['actions'].length;
|
replayer['timer']['actions'].length;
|
||||||
`);
|
`);
|
||||||
expect(actionLength).to.equal(
|
expect(actionLength).to.equal(
|
||||||
styleSheetRuleEvents.filter((e) => e.timestamp - styleSheetRuleEvents[0].timestamp >= 1500).length,
|
styleSheetRuleEvents.filter(
|
||||||
|
(e) => e.timestamp - styleSheetRuleEvents[0].timestamp >= 1500,
|
||||||
|
).length,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can handle removing style elements', async () => {
|
it('can handle removing style elements', async () => {
|
||||||
await this.page.evaluate(`events = ${JSON.stringify(stylesheetRemoveEvents)}`);
|
await this.page.evaluate(
|
||||||
|
`events = ${JSON.stringify(stylesheetRemoveEvents)}`,
|
||||||
|
);
|
||||||
const actionLength = await this.page.evaluate(`
|
const actionLength = await this.page.evaluate(`
|
||||||
const { Replayer } = rrweb;
|
const { Replayer } = rrweb;
|
||||||
const replayer = new Replayer(events);
|
const replayer = new Replayer(events);
|
||||||
@@ -158,7 +164,9 @@ describe('replayer', function (this: ISuite) {
|
|||||||
replayer['timer']['actions'].length;
|
replayer['timer']['actions'].length;
|
||||||
`);
|
`);
|
||||||
expect(actionLength).to.equal(
|
expect(actionLength).to.equal(
|
||||||
stylesheetRemoveEvents.filter((e) => e.timestamp - stylesheetRemoveEvents[0].timestamp >= 2500).length,
|
stylesheetRemoveEvents.filter(
|
||||||
|
(e) => e.timestamp - stylesheetRemoveEvents[0].timestamp >= 2500,
|
||||||
|
).length,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
13
typings/record/iframe-manager.d.ts
vendored
Normal file
13
typings/record/iframe-manager.d.ts
vendored
Normal 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;
|
||||||
|
}
|
||||||
6
typings/record/mutation.d.ts
vendored
6
typings/record/mutation.d.ts
vendored
@@ -1,7 +1,9 @@
|
|||||||
import { MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot';
|
import { MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot';
|
||||||
import { mutationRecord, blockClass, mutationCallBack } from '../types';
|
import { mutationRecord, blockClass, mutationCallBack } from '../types';
|
||||||
|
import { IframeManager } from './iframe-manager';
|
||||||
export default class MutationBuffer {
|
export default class MutationBuffer {
|
||||||
private frozen;
|
private frozen;
|
||||||
|
private locked;
|
||||||
private texts;
|
private texts;
|
||||||
private attributes;
|
private attributes;
|
||||||
private removes;
|
private removes;
|
||||||
@@ -17,7 +19,9 @@ export default class MutationBuffer {
|
|||||||
private maskInputOptions;
|
private maskInputOptions;
|
||||||
private recordCanvas;
|
private recordCanvas;
|
||||||
private slimDOMOptions;
|
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;
|
freeze(): void;
|
||||||
unfreeze(): void;
|
unfreeze(): void;
|
||||||
isFrozen(): boolean;
|
isFrozen(): boolean;
|
||||||
|
|||||||
2
typings/record/observer.d.ts
vendored
2
typings/record/observer.d.ts
vendored
@@ -1,5 +1,5 @@
|
|||||||
import { observerParam, listenerHandler, hooksParam } from '../types';
|
import { observerParam, listenerHandler, hooksParam } from '../types';
|
||||||
import MutationBuffer from './mutation';
|
import MutationBuffer from './mutation';
|
||||||
export declare const mutationBuffer: MutationBuffer;
|
export declare const mutationBuffers: MutationBuffer[];
|
||||||
export declare const INPUT_TAGS: string[];
|
export declare const INPUT_TAGS: string[];
|
||||||
export declare function initObservers(o: observerParam, hooks?: hooksParam): listenerHandler;
|
export declare function initObservers(o: observerParam, hooks?: hooksParam): listenerHandler;
|
||||||
|
|||||||
4
typings/replay/index.d.ts
vendored
4
typings/replay/index.d.ts
vendored
@@ -19,6 +19,7 @@ export declare class Replayer {
|
|||||||
private fragmentParentMap;
|
private fragmentParentMap;
|
||||||
private elementStateMap;
|
private elementStateMap;
|
||||||
private imageMap;
|
private imageMap;
|
||||||
|
private newDocumentQueue;
|
||||||
constructor(events: Array<eventWithTime | string>, config?: Partial<playerConfig>);
|
constructor(events: Array<eventWithTime | string>, config?: Partial<playerConfig>);
|
||||||
on(event: string, handler: Handler): this;
|
on(event: string, handler: Handler): this;
|
||||||
setConfig(config: Partial<playerConfig>): void;
|
setConfig(config: Partial<playerConfig>): void;
|
||||||
@@ -36,6 +37,9 @@ export declare class Replayer {
|
|||||||
private handleResize;
|
private handleResize;
|
||||||
private getCastFn;
|
private getCastFn;
|
||||||
private rebuildFullSnapshot;
|
private rebuildFullSnapshot;
|
||||||
|
private insertStyleRules;
|
||||||
|
private attachDocumentToIframe;
|
||||||
|
private collectIframeAndAttachDocument;
|
||||||
private waitForStylesheetLoad;
|
private waitForStylesheetLoad;
|
||||||
private preloadAllImages;
|
private preloadAllImages;
|
||||||
private applyIncremental;
|
private applyIncremental;
|
||||||
|
|||||||
13
typings/types.d.ts
vendored
13
typings/types.d.ts
vendored
@@ -2,6 +2,7 @@
|
|||||||
import { serializedNodeWithId, idNodeMap, INode, MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot';
|
import { serializedNodeWithId, idNodeMap, INode, MaskInputOptions, SlimDOMOptions } from 'rrweb-snapshot';
|
||||||
import { PackFn, UnpackFn } from './packer/base';
|
import { PackFn, UnpackFn } from './packer/base';
|
||||||
import { FontFaceDescriptors } from 'css-font-loading-module';
|
import { FontFaceDescriptors } from 'css-font-loading-module';
|
||||||
|
import { IframeManager } from './record/iframe-manager';
|
||||||
export declare enum EventType {
|
export declare enum EventType {
|
||||||
DomContentLoaded = 0,
|
DomContentLoaded = 0,
|
||||||
Load = 1,
|
Load = 1,
|
||||||
@@ -81,7 +82,7 @@ export declare type scrollData = {
|
|||||||
} & scrollPosition;
|
} & scrollPosition;
|
||||||
export declare type viewportResizeData = {
|
export declare type viewportResizeData = {
|
||||||
source: IncrementalSource.ViewportResize;
|
source: IncrementalSource.ViewportResize;
|
||||||
} & viewportResizeDimention;
|
} & viewportResizeDimension;
|
||||||
export declare type inputData = {
|
export declare type inputData = {
|
||||||
source: IncrementalSource.Input;
|
source: IncrementalSource.Input;
|
||||||
id: number;
|
id: number;
|
||||||
@@ -157,6 +158,8 @@ export declare type observerParam = {
|
|||||||
recordCanvas: boolean;
|
recordCanvas: boolean;
|
||||||
collectFonts: boolean;
|
collectFonts: boolean;
|
||||||
slimDOMOptions: SlimDOMOptions;
|
slimDOMOptions: SlimDOMOptions;
|
||||||
|
doc: Document;
|
||||||
|
iframeManager: IframeManager;
|
||||||
};
|
};
|
||||||
export declare type hooksParam = {
|
export declare type hooksParam = {
|
||||||
mutation?: mutationCallBack;
|
mutation?: mutationCallBack;
|
||||||
@@ -304,11 +307,11 @@ export declare type LogParam = {
|
|||||||
};
|
};
|
||||||
export declare type fontCallback = (p: fontParam) => void;
|
export declare type fontCallback = (p: fontParam) => void;
|
||||||
export declare type logCallback = (p: LogParam) => void;
|
export declare type logCallback = (p: LogParam) => void;
|
||||||
export declare type viewportResizeDimention = {
|
export declare type viewportResizeDimension = {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
};
|
};
|
||||||
export declare type viewportResizeCallback = (d: viewportResizeDimention) => void;
|
export declare type viewportResizeCallback = (d: viewportResizeDimension) => void;
|
||||||
export declare type inputValue = {
|
export declare type inputValue = {
|
||||||
text: string;
|
text: string;
|
||||||
isChecked: boolean;
|
isChecked: boolean;
|
||||||
@@ -325,6 +328,10 @@ export declare type mediaInteractionParam = {
|
|||||||
id: number;
|
id: number;
|
||||||
};
|
};
|
||||||
export declare type mediaInteractionCallback = (p: mediaInteractionParam) => void;
|
export declare type mediaInteractionCallback = (p: mediaInteractionParam) => void;
|
||||||
|
export declare type DocumentDimension = {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
};
|
||||||
export declare type Mirror = {
|
export declare type Mirror = {
|
||||||
map: idNodeMap;
|
map: idNodeMap;
|
||||||
getId: (n: INode) => number;
|
getId: (n: INode) => number;
|
||||||
|
|||||||
13
typings/utils.d.ts
vendored
13
typings/utils.d.ts
vendored
@@ -1,5 +1,5 @@
|
|||||||
import { Mirror, throttleOptions, listenerHandler, hookResetter, blockClass, eventWithTime, addedNodeMutation, removedNodeMutation, textMutation, attributeMutation, mutationData, scrollData, inputData } from './types';
|
import { Mirror, throttleOptions, listenerHandler, hookResetter, blockClass, eventWithTime, addedNodeMutation, removedNodeMutation, textMutation, attributeMutation, mutationData, scrollData, inputData, DocumentDimension } from './types';
|
||||||
import { INode } from 'rrweb-snapshot';
|
import { INode, serializedNodeWithId } from 'rrweb-snapshot';
|
||||||
export declare function on(type: string, fn: EventListenerOrEventListenerObject, target?: Document | Window): listenerHandler;
|
export declare function on(type: string, fn: EventListenerOrEventListenerObject, target?: Document | Window): listenerHandler;
|
||||||
export declare const mirror: Mirror;
|
export declare const mirror: Mirror;
|
||||||
export declare function throttle<T>(func: (arg: T) => void, wait: number, options?: throttleOptions): (arg: T) => void;
|
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 queueToResolveTrees(queue: addedNodeMutation[]): ResolveTree[];
|
||||||
export declare function iterateResolveTree(tree: ResolveTree, cb: (mutation: addedNodeMutation) => unknown): void;
|
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 {};
|
export {};
|
||||||
|
|||||||
@@ -2749,10 +2749,10 @@ rollup@^2.3.3:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.1.2"
|
fsevents "~2.1.2"
|
||||||
|
|
||||||
rrweb-snapshot@^1.0.4:
|
rrweb-snapshot@^1.0.6:
|
||||||
version "1.0.4"
|
version "1.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/rrweb-snapshot/-/rrweb-snapshot-1.0.4.tgz#608acf0f066e4f72213109ee6bfe25d291e7eb0b"
|
resolved "https://registry.yarnpkg.com/rrweb-snapshot/-/rrweb-snapshot-1.0.6.tgz#2b1143d2e8b5bba54c54bd63e485cfc253763b8e"
|
||||||
integrity sha512-I9oeK+LPeFE8N8MT011gBLEb+3VjnU3TJNHphzqDu5XJLna8bvMx9tkTiyD9MOlVCX80Le/MwSBxoJMpCX9uQA==
|
integrity sha512-ctwaNjFnomNFe446gOcMusSzIxLUnF0kq6ev0iuKa1W1lhiVcBoRdfpE8zDDRKee2MxHxt0rq2T5DMKSIAD6fQ==
|
||||||
|
|
||||||
run-async@^2.2.0:
|
run-async@^2.2.0:
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user