fix iframe parent issue

This commit is contained in:
Андрей Бабушкин 2025-04-17 13:50:46 +02:00
parent a9701ba57d
commit c04822bc0f
4 changed files with 44 additions and 28 deletions

View file

@ -31,6 +31,7 @@ import Message, {
Type as MType,
UserID,
WSChannel,
RemoveNode,
} from './messages.gen.js'
import Nodes from './nodes/index.js'
import type { Options as ObserverOptions } from './observer/top_observer.js'
@ -41,6 +42,7 @@ import type { Options as SessOptions } from './session.js'
import Session from './session.js'
import Ticker from './ticker.js'
import { MaintainerOptions } from './nodes/maintainer.js'
import vElTree from './observer/vTree.js'
interface TypedWorker extends Omit<Worker, 'postMessage'> {
postMessage(data: ToWorkerData): void
@ -273,6 +275,10 @@ export default class App {
'usability-test': true,
}
private emptyBatchCounter = 0
private readonly vTree = new vElTree((id: number) => {
this.nodes.unregisterNodeById(id)
this.send(RemoveNode(id))
})
constructor(
projectKey: string,
@ -362,7 +368,7 @@ export default class App {
forceNgOff: Boolean(options.forceNgOff),
maintainer: this.options.nodes?.maintainer,
})
this.observer = new Observer({ app: this, options })
this.observer = new Observer({ app: this, options, vTree: this.vTree })
this.ticker = new Ticker(this)
this.ticker.attach(() => this.commit())
this.debug = new Logger(this.options.__debug__)

View file

@ -20,8 +20,9 @@ import {
isUseElement,
hasTag,
isCommentNode,
isDocument,
} from '../guards.js'
import vElTree from './vTree.js'
import VirtualNodeTree from './vTree.js'
const iconCache = {}
const svgUrlCache = {}
@ -183,10 +184,10 @@ enum RecentsType {
export default abstract class Observer {
/** object tree where key is node id, value is null if it has no children, or object with same structure */
private readonly vTree = new vElTree((id: number) => {
this.app.nodes.unregisterNodeById(id)
this.app.send(RemoveNode(id))
})
// private readonly vTree = new vElTree((id: number) => {
// this.app.nodes.unregisterNodeById(id)
// this.app.send(RemoveNode(id))
// })
private readonly observer: MutationObserver
private readonly commited: Array<boolean | undefined> = []
private readonly recents: Map<number, RecentsType> = new Map()
@ -199,7 +200,9 @@ export default abstract class Observer {
protected readonly app: App,
protected readonly isTopContext = false,
options: { disableSprites: boolean } = { disableSprites: false },
public vTree: VirtualNodeTree,
) {
this.vTree = vTree
this.disableSprites = options.disableSprites
this.observer = createMutationObserver(
this.app.safe((mutations) => {
@ -513,7 +516,6 @@ export default abstract class Observer {
;(el as HTMLElement | SVGElement).style.width = `${width}px`
;(el as HTMLElement | SVGElement).style.height = `${height}px`
}
this.vTree.addNode(id, parentID ?? null)
this.app.send(CreateElementNode(id, parentID, index, el.tagName, isSVGElement(node)))
}
for (let i = 0; i < el.attributes.length; i++) {
@ -521,7 +523,6 @@ export default abstract class Observer {
this.sendNodeAttribute(id, el, attr.nodeName, attr.value)
}
} else if (isTextNode(node)) {
this.vTree.addNode(id, parentID ?? null)
// for text node id != 0, hence parentID !== undefined and parent is Element
this.app.send(CreateTextNode(id, parentID as number, index))
this.sendNodeData(id, parent as Element, node.data)

View file

@ -23,8 +23,9 @@ export default class TopObserver extends Observer {
private readonly options: Options
private readonly iframeOffsets: IFrameOffsets = new IFrameOffsets()
readonly app: App
public iframes: Map<number, HTMLIFrameElement> = new Map()
constructor(params: { app: App; options: Partial<Options> }) {
constructor(params: { app: App; options: Partial<Options>; vTree: any }) {
const opts = Object.assign(
{
captureIFrames: true,
@ -32,17 +33,37 @@ export default class TopObserver extends Observer {
},
params.options,
)
super(params.app, true, opts)
super(params.app, true, opts, params.vTree)
this.app = params.app
this.options = opts
this.vTree = params.vTree
// IFrames
this.app.nodes.attachNodeCallback((node) => {
const nodeId = this.app.nodes.getID(node);
if (
hasTag(node, 'iframe') &&
((this.options.captureIFrames && !hasOpenreplayAttribute(node, 'obscured')) ||
hasOpenreplayAttribute(node, 'capture'))
) {
this.handleIframe(node)
if (nodeId) {
this.iframes.set(nodeId, node)
}
}
if (nodeId || nodeId === 0) {
if (node.parentNode) {
const parentId = this.app.nodes.getID(node.parentNode)
this.vTree.addNode(nodeId, parentId ?? null)
}
else {
for (const iframe of this.iframes.entries()) {
const [iframeId, iframeElement] = iframe
if (iframeElement?.contentDocument === node) {
this.vTree.addNode(nodeId, iframeId)
this.iframes.delete(iframeId)
}
}
}
}
})
@ -86,7 +107,7 @@ export default class TopObserver extends Observer {
this.app.debug.info('doc already observed for', id)
return
}
const observer = new IFrameObserver(this.app)
const observer = new IFrameObserver(this.app, false, undefined, this.vTree)
this.iframeObservers.set(iframe, observer)
this.docObservers.set(currentDoc, observer)
this.iframeObserversArr.push(observer)
@ -114,7 +135,7 @@ export default class TopObserver extends Observer {
private shadowRootObservers: WeakMap<ShadowRoot, ShadowRootObserver> = new WeakMap()
private handleShadowRoot(shRoot: ShadowRoot) {
const observer = new ShadowRootObserver(this.app)
const observer = new ShadowRootObserver(this.app, false, undefined, this.vTree)
this.shadowRootObservers.set(shRoot, observer)
observer.observe(shRoot.host)
}
@ -130,6 +151,7 @@ export default class TopObserver extends Observer {
return shadow
}
this.app.nodes.clear()
this.vTree.clearAll()
// Can observe documentElement (<html>) here, because it is not supposed to be changing.
// However, it is possible in some exotic cases and may cause an ignorance of the newly created <html>
// In this case context.document have to be observed, but this will cause
@ -157,7 +179,7 @@ export default class TopObserver extends Observer {
}
this.app.nodes.clear()
this.app.nodes.syntheticMode(frameOder)
const iframeObserver = new IFrameObserver(this.app)
const iframeObserver = new IFrameObserver(this.app, false, undefined, this.vTree)
this.iframeObservers.set(window.document, iframeObserver)
iframeObserver.syntheticObserve(rootNodeId, window.document)
}
@ -168,8 +190,10 @@ export default class TopObserver extends Observer {
this.iframeObserversArr.forEach((observer) => observer.disconnect())
this.iframeObserversArr = []
this.iframeObservers = new WeakMap()
this.iframes.clear()
this.shadowRootObservers = new WeakMap()
this.docObservers = new WeakMap()
super.disconnect()
this.vTree.clearAll()
}
}

View file

@ -7,7 +7,6 @@ export default class VirtualNodeTree {
addNode(id: number, parentId: number | null = null) {
this.nodeParents[id] = parentId;
console.log(id, parentId, this.tree, this.nodeParents)
if (parentId === null) {
this.tree[id] = null;
} else {
@ -21,7 +20,6 @@ export default class VirtualNodeTree {
}
removeNode(id: number) {
console.log('del', id, this)
if (!(id in this.nodeParents)) {
// throw new Error(`Node ${id} doesn't exist`); ? since its just for tracking, nothing
return;
@ -107,16 +105,3 @@ export default class VirtualNodeTree {
this.nodeParents = {};
}
}
// TODO add tests
// const tree = new VirtualNodeTree();
// tree.addNode(0);
// tree.addNode(1, 0);
// tree.addNode(2, 1);
// tree.addNode(3, 1);
// tree.addNode(4, 3);
// console.log({ ...tree.tree });
// tree.removeNode(1)
// console.log(tree.tree)