fix(tracker): iframe scrolls
This commit is contained in:
parent
fcc7b27c61
commit
c7072220b5
7 changed files with 73 additions and 55 deletions
|
|
@ -1,3 +1,8 @@
|
|||
//@ts-ignore
|
||||
export function isNode(sth: any): sth is Node {
|
||||
return !!sth && sth.nodeType != null
|
||||
}
|
||||
|
||||
export function isSVGElement(node: Element): node is SVGElement {
|
||||
return node.namespaceURI === 'http://www.w3.org/2000/svg'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,20 +12,18 @@ export default class Nodes {
|
|||
attachNodeCallback(nodeCallback: NodeCallback): void {
|
||||
this.nodeCallbacks.push(nodeCallback)
|
||||
}
|
||||
// TODO: what is the difference with app.attachEventListener. can we use only one of those?
|
||||
attachElementListener(type: string, node: Element, elementListener: EventListener): void {
|
||||
attachNodeListener(type: string, node: Node, listener: EventListener): void {
|
||||
const id = this.getID(node)
|
||||
if (id === undefined) {
|
||||
return
|
||||
}
|
||||
node.addEventListener(type, elementListener)
|
||||
node.addEventListener(type, listener)
|
||||
let listeners = this.elementListeners.get(id)
|
||||
if (listeners === undefined) {
|
||||
listeners = []
|
||||
this.elementListeners.set(id, listeners)
|
||||
return
|
||||
}
|
||||
listeners.push([type, elementListener])
|
||||
listeners.push([type, listener])
|
||||
}
|
||||
|
||||
registerNode(node: Node): [/*id:*/ number, /*isNew:*/ boolean] {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export default class IFrameObserver extends Observer {
|
|||
} //log TODO common app.logger
|
||||
// Have to observe document, because the inner <html> might be changed
|
||||
this.observeRoot(doc, (docID) => {
|
||||
//MBTODO: do not send if empty (send on load? it might be in-place iframe, like our replayer, which does not get loaded)
|
||||
if (docID === undefined) {
|
||||
console.log('OpenReplay: Iframe document not bound')
|
||||
return
|
||||
|
|
|
|||
|
|
@ -78,41 +78,45 @@ export default class TopObserver extends Observer {
|
|||
private handleIframe(iframe: HTMLIFrameElement): void {
|
||||
let doc: Document | null = null
|
||||
let win: Window | null = null
|
||||
const handle = this.app.safe(() => {
|
||||
const id = this.app.nodes.getID(iframe)
|
||||
if (id === undefined) {
|
||||
//log
|
||||
return
|
||||
}
|
||||
const currentWin = iframe.contentWindow
|
||||
const currentDoc = iframe.contentDocument
|
||||
if (currentDoc && currentDoc !== doc) {
|
||||
const observer = new IFrameObserver(this.app)
|
||||
this.iframeObservers.push(observer)
|
||||
observer.observe(iframe)
|
||||
doc = currentDoc
|
||||
// setTimeout is required. Otherwise some event listeners (scroll, mousemove) applied in modules
|
||||
// do not work on the iframe document when it 've been loaded dynamically ((why?))
|
||||
const handle = this.app.safe(() =>
|
||||
setTimeout(() => {
|
||||
const id = this.app.nodes.getID(iframe)
|
||||
if (id === undefined) {
|
||||
//log
|
||||
return
|
||||
}
|
||||
const currentWin = iframe.contentWindow
|
||||
const currentDoc = iframe.contentDocument
|
||||
if (currentDoc && currentDoc !== doc) {
|
||||
const observer = new IFrameObserver(this.app)
|
||||
this.iframeObservers.push(observer)
|
||||
observer.observe(iframe) // TODO: call unregisterNode for the previous doc if present (incapsulate: one iframe - one observer)
|
||||
doc = currentDoc
|
||||
|
||||
// Le truc
|
||||
;(doc as PatchedDocument).__openreplay__getOffset = () => {
|
||||
const { top, left } = this.getDocumentOffset(iframe.ownerDocument)
|
||||
return {
|
||||
top: iframe.offsetTop + top,
|
||||
left: iframe.offsetLeft + left,
|
||||
// Le truc
|
||||
;(doc as PatchedDocument).__openreplay__getOffset = () => {
|
||||
const { top, left } = this.getDocumentOffset(iframe.ownerDocument)
|
||||
return {
|
||||
top: iframe.offsetTop + top,
|
||||
left: iframe.offsetLeft + left,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
currentWin &&
|
||||
// Sometimes currentWin.window is null (not in specification). Such window object is not functional
|
||||
currentWin === currentWin.window &&
|
||||
!this.contextsSet.has(currentWin) // for each context callbacks called once per Tracker (TopObserver) instance
|
||||
) {
|
||||
this.contextsSet.add(currentWin)
|
||||
//@ts-ignore https://github.com/microsoft/TypeScript/issues/41684
|
||||
this.contextCallbacks.forEach((cb) => cb(currentWin))
|
||||
win = currentWin
|
||||
}
|
||||
})
|
||||
if (
|
||||
currentWin &&
|
||||
// Sometimes currentWin.window is null (not in specification). Such window object is not functional
|
||||
currentWin === currentWin.window &&
|
||||
!this.contextsSet.has(currentWin) // for each context callbacks called once per Tracker (TopObserver) instance
|
||||
) {
|
||||
this.contextsSet.add(currentWin)
|
||||
//@ts-ignore https://github.com/microsoft/TypeScript/issues/41684
|
||||
this.contextCallbacks.forEach((cb) => cb(currentWin))
|
||||
win = currentWin
|
||||
}
|
||||
}, 0),
|
||||
)
|
||||
iframe.addEventListener('load', handle) // why app.attachEventListener not working?
|
||||
handle()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,8 +97,8 @@ export default function (app: App): void {
|
|||
if (!hasTag(node, 'IMG')) {
|
||||
return
|
||||
}
|
||||
app.nodes.attachElementListener('error', node, sendImgAttrs.bind(node))
|
||||
app.nodes.attachElementListener('load', node, sendImgAttrs.bind(node))
|
||||
app.nodes.attachNodeListener('error', node, sendImgAttrs.bind(node))
|
||||
app.nodes.attachNodeListener('load', node, sendImgAttrs.bind(node))
|
||||
sendImgAttrs.call(node)
|
||||
observer.observe(node, { attributes: true, attributeFilter: ['src', 'srcset'] })
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,30 +1,39 @@
|
|||
import type App from '../app/index.js'
|
||||
import { SetViewportScroll, SetNodeScroll } from '../app/messages.gen.js'
|
||||
import { isElementNode, isRootNode } from '../app/guards.js'
|
||||
import { isNode, isElementNode, isRootNode, isDocument } from '../app/guards.js'
|
||||
|
||||
function getDocumentScroll(doc: Document): [number, number] {
|
||||
const win = doc.defaultView
|
||||
return [
|
||||
(win && win.pageXOffset) ||
|
||||
(doc.documentElement && doc.documentElement.scrollLeft) ||
|
||||
(doc.body && doc.body.scrollLeft) ||
|
||||
0,
|
||||
(win && win.pageYOffset) ||
|
||||
(doc.documentElement && doc.documentElement.scrollTop) ||
|
||||
(doc.body && doc.body.scrollTop) ||
|
||||
0,
|
||||
]
|
||||
}
|
||||
|
||||
export default function (app: App): void {
|
||||
let documentScroll = false
|
||||
const nodeScroll: Map<Node, [number, number]> = new Map()
|
||||
|
||||
function setNodeScroll(target: EventTarget | null) {
|
||||
if (target instanceof Element) {
|
||||
if (!isNode(target)) {
|
||||
return
|
||||
}
|
||||
if (isElementNode(target)) {
|
||||
nodeScroll.set(target, [target.scrollLeft, target.scrollTop])
|
||||
}
|
||||
if (isDocument(target)) {
|
||||
nodeScroll.set(target, getDocumentScroll(target))
|
||||
}
|
||||
}
|
||||
|
||||
const sendSetViewportScroll = app.safe((): void =>
|
||||
app.send(
|
||||
SetViewportScroll(
|
||||
window.pageXOffset ||
|
||||
(document.documentElement && document.documentElement.scrollLeft) ||
|
||||
(document.body && document.body.scrollLeft) ||
|
||||
0,
|
||||
window.pageYOffset ||
|
||||
(document.documentElement && document.documentElement.scrollTop) ||
|
||||
(document.body && document.body.scrollTop) ||
|
||||
0,
|
||||
),
|
||||
),
|
||||
app.send(SetViewportScroll(...getDocumentScroll(document))),
|
||||
)
|
||||
|
||||
const sendSetNodeScroll = app.safe((s: [number, number], node: Node): void => {
|
||||
|
|
@ -42,11 +51,12 @@ export default function (app: App): void {
|
|||
})
|
||||
|
||||
app.nodes.attachNodeCallback((node, isStart) => {
|
||||
// MBTODO: iterate over all the nodes on start instead of using isStart hack
|
||||
if (isStart && isElementNode(node) && node.scrollLeft + node.scrollTop > 0) {
|
||||
nodeScroll.set(node, [node.scrollLeft, node.scrollTop])
|
||||
} else if (isRootNode(node)) {
|
||||
// scroll is not-composed event (https://javascript.info/shadow-dom-events)
|
||||
app.attachEventListener(node, 'scroll', (e: Event): void => {
|
||||
app.nodes.attachNodeListener('scroll', node, (e: Event): void => {
|
||||
setNodeScroll(e.target)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export default class BatchWriter {
|
|||
return
|
||||
}
|
||||
|
||||
// MBTODO: move service-messages creation to webworker
|
||||
// MBTODO: move service-messages creation methods to webworker
|
||||
const batchMetadata: Messages.BatchMetadata = [
|
||||
Messages.Type.BatchMetadata,
|
||||
1,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue