impl replay the mutations and mouse interactions
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -6,8 +6,10 @@ import {
|
|||||||
IncrementalSource,
|
IncrementalSource,
|
||||||
fullSnapshotEvent,
|
fullSnapshotEvent,
|
||||||
eventWithTime,
|
eventWithTime,
|
||||||
|
MouseInteractions,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import eventsStr from './events';
|
import eventsStr from './events';
|
||||||
|
import { mirror, getIdNodeMap } from '../utils';
|
||||||
|
|
||||||
const _events: eventWithTime[] = JSON.parse(eventsStr);
|
const _events: eventWithTime[] = JSON.parse(eventsStr);
|
||||||
|
|
||||||
@@ -94,13 +96,57 @@ class Replayer {
|
|||||||
.replace(/>/g, '>'),
|
.replace(/>/g, '>'),
|
||||||
);
|
);
|
||||||
this.iframe.contentDocument!.close();
|
this.iframe.contentDocument!.close();
|
||||||
|
mirror.map = getIdNodeMap(this.iframe.contentDocument!);
|
||||||
|
// avoid form submit to refresh the iframe
|
||||||
|
this.iframe.contentDocument!.querySelectorAll('form').forEach(form => {
|
||||||
|
form.addEventListener('submit', evt => evt.preventDefault());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyIncremental(d: incrementalData) {
|
private applyIncremental(d: incrementalData) {
|
||||||
switch (d.source) {
|
switch (d.source) {
|
||||||
case IncrementalSource.Mutation:
|
case IncrementalSource.Mutation: {
|
||||||
|
d.texts.forEach(mutation => {
|
||||||
|
const target = (mirror.getNode(mutation.id) as Node) as Text;
|
||||||
|
target.textContent = mutation.value;
|
||||||
|
});
|
||||||
|
d.attributes.forEach(mutation => {
|
||||||
|
const target = (mirror.getNode(mutation.id) as Node) as Element;
|
||||||
|
for (const attributeName in mutation.attributes) {
|
||||||
|
if (typeof attributeName === 'string') {
|
||||||
|
const value = mutation.attributes[attributeName];
|
||||||
|
if (value) {
|
||||||
|
target.setAttribute(attributeName, value);
|
||||||
|
} else {
|
||||||
|
target.removeAttribute(attributeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// TODO: update id node map
|
||||||
|
d.removes.forEach(mutation => {
|
||||||
|
const target = (mirror.getNode(mutation.id) as Node) as Element;
|
||||||
|
const parent = (mirror.getNode(mutation.parentId) as Node) as Element;
|
||||||
|
parent.removeChild(target);
|
||||||
|
});
|
||||||
|
d.adds.forEach(mutation => {
|
||||||
|
const target = (mirror.getNode(mutation.id) as Node) as Element;
|
||||||
|
const parent = (mirror.getNode(mutation.parentId) as Node) as Element;
|
||||||
|
if (mutation.nextId) {
|
||||||
|
const next = (mirror.getNode(mutation.nextId) as Node) as Element;
|
||||||
|
parent.insertBefore(target, next);
|
||||||
|
} else if (mutation.previousId) {
|
||||||
|
const previous = (mirror.getNode(
|
||||||
|
mutation.previousId,
|
||||||
|
) as Node) as Element;
|
||||||
|
parent.insertBefore(target, previous.nextSibling);
|
||||||
|
} else {
|
||||||
|
parent.appendChild(target);
|
||||||
|
}
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case IncrementalSource.MouseMove:
|
case IncrementalSource.MouseMove:
|
||||||
d.positions.forEach(p => {
|
d.positions.forEach(p => {
|
||||||
later(() => {
|
later(() => {
|
||||||
@@ -109,8 +155,19 @@ class Replayer {
|
|||||||
}, p.timeOffset);
|
}, p.timeOffset);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case IncrementalSource.MouseInteraction:
|
case IncrementalSource.MouseInteraction: {
|
||||||
|
const event = new Event(MouseInteractions[d.type].toLowerCase());
|
||||||
|
const target = (mirror.getNode(d.id) as Node) as HTMLElement;
|
||||||
|
target.dispatchEvent(event);
|
||||||
|
if (d.type === MouseInteractions.Blur) {
|
||||||
|
target.blur();
|
||||||
|
} else if (d.type === MouseInteractions.Click) {
|
||||||
|
target.click();
|
||||||
|
} else if (d.type === MouseInteractions.Focus) {
|
||||||
|
target.focus();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case IncrementalSource.Scroll:
|
case IncrementalSource.Scroll:
|
||||||
// TODO: maybe element
|
// TODO: maybe element
|
||||||
this.iframe.contentWindow!.scrollTo({
|
this.iframe.contentWindow!.scrollTo({
|
||||||
@@ -124,13 +181,11 @@ class Replayer {
|
|||||||
this.iframe.height = `${d.height}px`;
|
this.iframe.height = `${d.height}px`;
|
||||||
break;
|
break;
|
||||||
case IncrementalSource.Input: {
|
case IncrementalSource.Input: {
|
||||||
const target: HTMLInputElement | null = this.iframe.contentDocument!.querySelector(
|
const target: HTMLInputElement = (mirror.getNode(
|
||||||
`[data-rrid="${d.id}"]`,
|
d.id,
|
||||||
);
|
) as Node) as HTMLInputElement;
|
||||||
if (target) {
|
target.checked = d.isChecked;
|
||||||
target.checked = d.isChecked;
|
target.value = d.text;
|
||||||
target.value = d.text;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
21
src/utils.ts
21
src/utils.ts
@@ -1,3 +1,4 @@
|
|||||||
|
import { idNodeMap, NodeType, serializeNodeWithId } from 'rrweb-snapshot';
|
||||||
import {
|
import {
|
||||||
Mirror,
|
Mirror,
|
||||||
throttleOptions,
|
throttleOptions,
|
||||||
@@ -29,6 +30,26 @@ export const mirror: Mirror = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: transform this into the snapshot repo
|
||||||
|
export function getIdNodeMap(doc: Document) {
|
||||||
|
const map: idNodeMap = {};
|
||||||
|
|
||||||
|
function walk(n: Node) {
|
||||||
|
const node = serializeNodeWithId(n, doc, map);
|
||||||
|
if (!node) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (node.type === NodeType.Document || node.type === NodeType.Element) {
|
||||||
|
for (const _n of Array.from(n.childNodes)) {
|
||||||
|
walk(_n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
walk(doc);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
// copy from underscore and modified
|
// copy from underscore and modified
|
||||||
export function throttle<T>(
|
export function throttle<T>(
|
||||||
func: (arg: T) => void,
|
func: (arg: T) => void,
|
||||||
|
|||||||
Reference in New Issue
Block a user