From c04822bc0fca967e624c81214d0a7cc7969f0a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B4=D1=80=D0=B5=D0=B9=20=D0=91=D0=B0=D0=B1?= =?UTF-8?q?=D1=83=D1=88=D0=BA=D0=B8=D0=BD?= Date: Thu, 17 Apr 2025 13:50:46 +0200 Subject: [PATCH] fix iframe parent issue --- tracker/tracker/src/main/app/index.ts | 8 ++++- .../tracker/src/main/app/observer/observer.ts | 15 ++++---- .../src/main/app/observer/top_observer.ts | 34 ++++++++++++++++--- .../tracker/src/main/app/observer/vTree.ts | 15 -------- 4 files changed, 44 insertions(+), 28 deletions(-) diff --git a/tracker/tracker/src/main/app/index.ts b/tracker/tracker/src/main/app/index.ts index 7684cefd8..90e073ea3 100644 --- a/tracker/tracker/src/main/app/index.ts +++ b/tracker/tracker/src/main/app/index.ts @@ -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 { 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__) diff --git a/tracker/tracker/src/main/app/observer/observer.ts b/tracker/tracker/src/main/app/observer/observer.ts index cb1fceb60..27ec6dbcc 100644 --- a/tracker/tracker/src/main/app/observer/observer.ts +++ b/tracker/tracker/src/main/app/observer/observer.ts @@ -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 = [] private readonly recents: Map = 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) diff --git a/tracker/tracker/src/main/app/observer/top_observer.ts b/tracker/tracker/src/main/app/observer/top_observer.ts index c5f623170..54aba1726 100644 --- a/tracker/tracker/src/main/app/observer/top_observer.ts +++ b/tracker/tracker/src/main/app/observer/top_observer.ts @@ -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 = new Map() - constructor(params: { app: App; options: Partial }) { + constructor(params: { app: App; options: Partial; 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 = 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 () 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 // 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() } } diff --git a/tracker/tracker/src/main/app/observer/vTree.ts b/tracker/tracker/src/main/app/observer/vTree.ts index 852fd16a8..b11b6b286 100644 --- a/tracker/tracker/src/main/app/observer/vTree.ts +++ b/tracker/tracker/src/main/app/observer/vTree.ts @@ -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)