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:
@@ -43,6 +43,7 @@ import {
|
||||
} from '../types';
|
||||
import MutationBuffer from './mutation';
|
||||
import { stringify } from './stringify';
|
||||
import { IframeManager } from './iframe-manager';
|
||||
|
||||
type WindowWithStoredMutationObserver = Window & {
|
||||
__rrMutationObserver?: MutationObserver;
|
||||
@@ -53,17 +54,21 @@ type WindowWithAngularZone = Window & {
|
||||
};
|
||||
};
|
||||
|
||||
export const mutationBuffer = new MutationBuffer();
|
||||
export const mutationBuffers: MutationBuffer[] = [];
|
||||
|
||||
function initMutationObserver(
|
||||
cb: mutationCallBack,
|
||||
doc: Document,
|
||||
blockClass: blockClass,
|
||||
blockSelector: string | null,
|
||||
inlineStylesheet: boolean,
|
||||
maskInputOptions: MaskInputOptions,
|
||||
recordCanvas: boolean,
|
||||
slimDOMOptions: SlimDOMOptions,
|
||||
iframeManager: IframeManager,
|
||||
): MutationObserver {
|
||||
const mutationBuffer = new MutationBuffer();
|
||||
mutationBuffers.push(mutationBuffer);
|
||||
// see mutation.ts for details
|
||||
mutationBuffer.init(
|
||||
cb,
|
||||
@@ -73,8 +78,10 @@ function initMutationObserver(
|
||||
maskInputOptions,
|
||||
recordCanvas,
|
||||
slimDOMOptions,
|
||||
doc,
|
||||
iframeManager,
|
||||
);
|
||||
let mutationBufferCtor =
|
||||
let mutationObserverCtor =
|
||||
window.MutationObserver ||
|
||||
/**
|
||||
* Some websites may disable MutationObserver by removing it from the window object.
|
||||
@@ -94,15 +101,15 @@ function initMutationObserver(
|
||||
angularZoneSymbol
|
||||
]
|
||||
) {
|
||||
mutationBufferCtor = ((window as unknown) as Record<
|
||||
mutationObserverCtor = ((window as unknown) as Record<
|
||||
string,
|
||||
typeof MutationObserver
|
||||
>)[angularZoneSymbol];
|
||||
}
|
||||
const observer = new mutationBufferCtor(
|
||||
const observer = new mutationObserverCtor(
|
||||
mutationBuffer.processMutations.bind(mutationBuffer),
|
||||
);
|
||||
observer.observe(document, {
|
||||
observer.observe(doc, {
|
||||
attributes: true,
|
||||
attributeOldValue: true,
|
||||
characterData: true,
|
||||
@@ -116,6 +123,7 @@ function initMutationObserver(
|
||||
function initMoveObserver(
|
||||
cb: mousemoveCallBack,
|
||||
sampling: SamplingStrategy,
|
||||
doc: Document,
|
||||
): listenerHandler {
|
||||
if (sampling.mousemove === false) {
|
||||
return () => {};
|
||||
@@ -161,8 +169,8 @@ function initMoveObserver(
|
||||
},
|
||||
);
|
||||
const handlers = [
|
||||
on('mousemove', updatePosition),
|
||||
on('touchmove', updatePosition),
|
||||
on('mousemove', updatePosition, doc),
|
||||
on('touchmove', updatePosition, doc),
|
||||
];
|
||||
return () => {
|
||||
handlers.forEach((h) => h());
|
||||
@@ -171,6 +179,7 @@ function initMoveObserver(
|
||||
|
||||
function initMouseInteractionObserver(
|
||||
cb: mouseInteractionCallBack,
|
||||
doc: Document,
|
||||
blockClass: blockClass,
|
||||
sampling: SamplingStrategy,
|
||||
): listenerHandler {
|
||||
@@ -211,7 +220,7 @@ function initMouseInteractionObserver(
|
||||
.forEach((eventKey: keyof typeof MouseInteractions) => {
|
||||
const eventName = eventKey.toLowerCase();
|
||||
const handler = getHandler(eventKey);
|
||||
handlers.push(on(eventName, handler));
|
||||
handlers.push(on(eventName, handler, doc));
|
||||
});
|
||||
return () => {
|
||||
handlers.forEach((h) => h());
|
||||
@@ -220,6 +229,7 @@ function initMouseInteractionObserver(
|
||||
|
||||
function initScrollObserver(
|
||||
cb: scrollCallback,
|
||||
doc: Document,
|
||||
blockClass: blockClass,
|
||||
sampling: SamplingStrategy,
|
||||
): listenerHandler {
|
||||
@@ -228,8 +238,8 @@ function initScrollObserver(
|
||||
return;
|
||||
}
|
||||
const id = mirror.getId(evt.target as INode);
|
||||
if (evt.target === document) {
|
||||
const scrollEl = (document.scrollingElement || document.documentElement)!;
|
||||
if (evt.target === doc) {
|
||||
const scrollEl = (doc.scrollingElement || doc.documentElement)!;
|
||||
cb({
|
||||
id,
|
||||
x: scrollEl.scrollLeft,
|
||||
@@ -270,6 +280,7 @@ export const INPUT_TAGS = ['INPUT', 'TEXTAREA', 'SELECT'];
|
||||
const lastInputValueMap: WeakMap<EventTarget, inputValue> = new WeakMap();
|
||||
function initInputObserver(
|
||||
cb: inputCallback,
|
||||
doc: Document,
|
||||
blockClass: blockClass,
|
||||
ignoreClass: string,
|
||||
maskInputOptions: MaskInputOptions,
|
||||
@@ -314,7 +325,7 @@ function initInputObserver(
|
||||
// the other radios with the same name attribute will be unchecked.
|
||||
const name: string | undefined = (target as HTMLInputElement).name;
|
||||
if (type === 'radio' && name && isChecked) {
|
||||
document
|
||||
doc
|
||||
.querySelectorAll(`input[type="radio"][name="${name}"]`)
|
||||
.forEach((el) => {
|
||||
if (el !== target) {
|
||||
@@ -344,7 +355,7 @@ function initInputObserver(
|
||||
const events = sampling.input === 'last' ? ['change'] : ['input', 'change'];
|
||||
const handlers: Array<
|
||||
listenerHandler | hookResetter
|
||||
> = events.map((eventName) => on(eventName, eventHandler));
|
||||
> = events.map((eventName) => on(eventName, eventHandler, doc));
|
||||
const propertyDescriptor = Object.getOwnPropertyDescriptor(
|
||||
HTMLInputElement.prototype,
|
||||
'value',
|
||||
@@ -727,27 +738,32 @@ export function initObservers(
|
||||
mergeHooks(o, hooks);
|
||||
const mutationObserver = initMutationObserver(
|
||||
o.mutationCb,
|
||||
o.doc,
|
||||
o.blockClass,
|
||||
o.blockSelector,
|
||||
o.inlineStylesheet,
|
||||
o.maskInputOptions,
|
||||
o.recordCanvas,
|
||||
o.slimDOMOptions,
|
||||
o.iframeManager,
|
||||
);
|
||||
const mousemoveHandler = initMoveObserver(o.mousemoveCb, o.sampling);
|
||||
const mousemoveHandler = initMoveObserver(o.mousemoveCb, o.sampling, o.doc);
|
||||
const mouseInteractionHandler = initMouseInteractionObserver(
|
||||
o.mouseInteractionCb,
|
||||
o.doc,
|
||||
o.blockClass,
|
||||
o.sampling,
|
||||
);
|
||||
const scrollHandler = initScrollObserver(
|
||||
o.scrollCb,
|
||||
o.doc,
|
||||
o.blockClass,
|
||||
o.sampling,
|
||||
);
|
||||
const viewportResizeHandler = initViewportResizeObserver(o.viewportResizeCb);
|
||||
const inputHandler = initInputObserver(
|
||||
o.inputCb,
|
||||
o.doc,
|
||||
o.blockClass,
|
||||
o.ignoreClass,
|
||||
o.maskInputOptions,
|
||||
|
||||
Reference in New Issue
Block a user