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.
This commit is contained in:
Lucky Feng
2021-05-02 21:56:43 +08:00
committed by GitHub
parent 2b96a68e88
commit 7e46341c18
2 changed files with 54 additions and 19 deletions

View File

@@ -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();
@@ -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,
@@ -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);
@@ -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