Skip to content

Commit

Permalink
fix: issue #548 (#550)
Browse files Browse the repository at this point in the history
1. Do not use virtual parent optimization if the mutation targets have iframe elements as children. This will cause some performance regression but will be easy to add and ship.
2. If an iframe element has already been a child of a virtual parent, add the virtual parent back to the dom.
  • Loading branch information
YunFeng0817 authored May 2, 2021
1 parent 2b96a68 commit 7e46341
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 19 deletions.
72 changes: 53 additions & 19 deletions src/replay/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,23 +166,9 @@ export class Replayer {
this.emitter.on(ReplayerEvents.Flush, () => {
const { scrollMap, inputMap } = this.treeIndex.flush();

for (const [frag, parent] of this.fragmentParentMap.entries()) {
mirror.map[parent.__sn.id] = parent;
/**
* If we have already set value attribute on textarea,
* then we could not apply text content as default value any more.
*/
if (
parent.__sn.type === NodeType.Element &&
parent.__sn.tagName === 'textarea' &&
frag.textContent
) {
((parent as unknown) as HTMLTextAreaElement).value = frag.textContent;
}
parent.appendChild(frag);
// restore state of elements after they are mounted
this.restoreState(parent);
}
this.fragmentParentMap.forEach((parent, frag) =>
this.restoreRealParent(frag, parent),
);
this.fragmentParentMap.clear();
this.elementStateMap.clear();

Expand Down Expand Up @@ -627,6 +613,20 @@ export class Replayer {
iframeEl: HTMLIFrameElement,
) {
const collected: AppendedIframe[] = [];
// If iframeEl is detached from dom, iframeEl.contentDocument is null.
if (!iframeEl.contentDocument) {
let parent = iframeEl.parentNode;
while (parent) {
// The parent of iframeEl is virtual parent and we need to mount it on the dom.
if (this.fragmentParentMap.has((parent as unknown) as INode)) {
const frag = (parent as unknown) as INode;
const realParent = this.fragmentParentMap.get(frag)!;
this.restoreRealParent(frag, realParent);
break;
}
parent = parent.parentNode;
}
}
buildNodeWithSN(mutation.node, {
doc: iframeEl.contentDocument!,
map: mirror.map,
Expand Down Expand Up @@ -1139,8 +1139,19 @@ export class Replayer {
parentInDocument = this.iframe.contentDocument.body.contains(parent);
}

// if parent element is an iframe, iframe document can't be appended to virtual parent
if (useVirtualParent && parentInDocument && !isIframeINode(parent)) {
const hasIframeChild =
((parent as unknown) as HTMLElement).getElementsByTagName?.('iframe')
.length > 0;
/**
* Why !isIframeINode(parent)? If parent element is an iframe, iframe document can't be appended to virtual parent.
* Why !hasIframeChild? If we move iframe elements from dom to fragment document, we will lose the contentDocument of iframe. So we need to disable the virtual dom optimization if a parent node contains iframe elements.
*/
if (
useVirtualParent &&
parentInDocument &&
!isIframeINode(parent) &&
!hasIframeChild
) {
const virtualParent = (document.createDocumentFragment() as unknown) as INode;
mirror.map[mutation.parentId] = virtualParent;
this.fragmentParentMap.set(virtualParent, parent);
Expand Down Expand Up @@ -1529,6 +1540,29 @@ export class Replayer {
});
}

/**
* Replace the virtual parent with the real parent.
* @param frag fragment document, the virtual parent
* @param parent real parent element
*/
private restoreRealParent(frag: INode, parent: INode) {
mirror.map[parent.__sn.id] = parent;
/**
* If we have already set value attribute on textarea,
* then we could not apply text content as default value any more.
*/
if (
parent.__sn.type === NodeType.Element &&
parent.__sn.tagName === 'textarea' &&
frag.textContent
) {
((parent as unknown) as HTMLTextAreaElement).value = frag.textContent;
}
parent.appendChild(frag);
// restore state of elements after they are mounted
this.restoreState(parent);
}

/**
* store state of elements before unmounted from dom recursively
* the state should be restored in the handler of event ReplayerEvents.Flush
Expand Down
1 change: 1 addition & 0 deletions typings/replay/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export declare class Replayer {
private hoverElements;
private isUserInteraction;
private backToNormal;
private restoreRealParent;
private storeState;
private restoreState;
private warnNodeNotFound;
Expand Down

0 comments on commit 7e46341

Please sign in to comment.