fix (tracker): 3.5.4: shadowDOM fix - extended 'inDocument' check
This commit is contained in:
parent
18a1071060
commit
be58d4e754
4 changed files with 58 additions and 34 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@openreplay/tracker",
|
||||
"description": "The OpenReplay tracker main package",
|
||||
"version": "3.5.3",
|
||||
"version": "3.5.4",
|
||||
"keywords": [
|
||||
"logging",
|
||||
"replay"
|
||||
|
|
|
|||
|
|
@ -41,32 +41,57 @@ export function isInstance<T extends WindowConstructor>(node: Node, constr: Cons
|
|||
// @ts-ignore (for EI, Safary)
|
||||
doc.parentWindow ||
|
||||
doc.defaultView; // TODO: smart global typing for Window object
|
||||
while(context.parent && context.parent !== context) {
|
||||
while((context.parent || context.top) && context.parent !== context) {
|
||||
// @ts-ignore
|
||||
if (node instanceof context[constr.name]) {
|
||||
return true
|
||||
}
|
||||
// @ts-ignore
|
||||
context = context.parent
|
||||
context = context.parent || context.top
|
||||
}
|
||||
// @ts-ignore
|
||||
return node instanceof context[constr.name]
|
||||
}
|
||||
|
||||
export function inDocument(node: Node): boolean {
|
||||
// TODO: ensure 1. it works in every cases (iframes/detached nodes) and 2. the most efficient
|
||||
export function inDocument(node: Node) {
|
||||
const doc = node.ownerDocument
|
||||
if (!doc) { return false }
|
||||
if (doc.contains(node)) { return true }
|
||||
let context: Window =
|
||||
// @ts-ignore (for EI, Safary)
|
||||
doc.parentWindow ||
|
||||
doc.defaultView;
|
||||
while(context.parent && context.parent !== context) {
|
||||
if (context.document.contains(node)) {
|
||||
if (!doc) { return true } // Document
|
||||
let current: Node | null = node
|
||||
while(current) {
|
||||
if (current === doc) {
|
||||
return true
|
||||
} else if(isInstance(current, ShadowRoot)) {
|
||||
current = current.host
|
||||
} else {
|
||||
current = current.parentNode
|
||||
}
|
||||
// @ts-ignore
|
||||
context = context.parent
|
||||
}
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
|
||||
// export function inDocument(node: Node): boolean {
|
||||
// // @ts-ignore compatability
|
||||
// if (node.getRootNode) {
|
||||
// let root: Node
|
||||
// while ((root = node.getRootNode()) !== node) {
|
||||
// ////
|
||||
// }
|
||||
// }
|
||||
|
||||
// const doc = node.ownerDocument
|
||||
// if (!doc) { return false }
|
||||
// if (doc.contains(node)) { return true }
|
||||
// let context: Window =
|
||||
// // @ts-ignore (for EI, Safary)
|
||||
// doc.parentWindow ||
|
||||
// doc.defaultView;
|
||||
// while(context.parent && context.parent !== context) {
|
||||
// if (context.document.contains(node)) {
|
||||
// return true
|
||||
// }
|
||||
// // @ts-ignore
|
||||
// context = context.parent
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { hasOpenreplayAttribute } from "../../utils.js";
|
||||
import {
|
||||
RemoveNodeAttribute,
|
||||
SetNodeAttribute,
|
||||
|
|
@ -59,9 +58,7 @@ export default abstract class Observer {
|
|||
private readonly indexes: Array<number> = [];
|
||||
private readonly attributesList: Array<Set<string> | undefined> = [];
|
||||
private readonly textSet: Set<number> = new Set();
|
||||
private readonly inUpperContext: boolean;
|
||||
constructor(protected readonly app: App, protected readonly context: Window = window) {
|
||||
this.inUpperContext = context.parent === context //TODO: get rid of context here
|
||||
constructor(protected readonly app: App, protected readonly isTopContext = false) {
|
||||
this.observer = new MutationObserver(
|
||||
this.app.safe((mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
|
|
@ -226,7 +223,7 @@ export default abstract class Observer {
|
|||
// Disable parent check for the upper context HTMLHtmlElement, because it is root there... (before)
|
||||
// TODO: get rid of "special" cases (there is an issue with CreateDocument altered behaviour though)
|
||||
// TODO: Clean the logic (though now it workd fine)
|
||||
if (!isInstance(node, HTMLHtmlElement) || !this.inUpperContext) {
|
||||
if (!isInstance(node, HTMLHtmlElement) || !this.isTopContext) {
|
||||
if (parent === null) {
|
||||
this.unbindNode(node);
|
||||
return false;
|
||||
|
|
@ -321,6 +318,8 @@ export default abstract class Observer {
|
|||
for (let id = 0; id < this.recents.length; id++) {
|
||||
// TODO: make things/logic nice here.
|
||||
// commit required in any case if recents[id] true or false (in case of unbinding) or undefined (in case of attr change).
|
||||
// Possible solution: separate new node commit (recents) and new attribute/move node commit
|
||||
// Otherwise commitNode is called on each node, which might be a lot
|
||||
if (!this.myNodes[id]) { continue }
|
||||
this.commitNode(id);
|
||||
if (this.recents[id] === true && (node = this.app.nodes.getNode(id))) {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import ShadowRootObserver from "./shadow_root_observer.js";
|
|||
|
||||
import { CreateDocument } from "../../../messages/index.js";
|
||||
import App from "../index.js";
|
||||
import { IN_BROWSER } from '../../utils.js'
|
||||
import { IN_BROWSER, hasOpenreplayAttribute } from '../../utils.js'
|
||||
|
||||
export interface Options {
|
||||
captureIFrames: boolean
|
||||
|
|
@ -17,15 +17,16 @@ const attachShadowNativeFn = IN_BROWSER ? Element.prototype.attachShadow : ()=>n
|
|||
export default class TopObserver extends Observer {
|
||||
private readonly options: Options;
|
||||
constructor(app: App, options: Partial<Options>) {
|
||||
super(app);
|
||||
super(app, true);
|
||||
this.options = Object.assign({
|
||||
captureIFrames: false
|
||||
captureIFrames: true
|
||||
}, options);
|
||||
|
||||
// IFrames
|
||||
this.app.nodes.attachNodeCallback(node => {
|
||||
if (isInstance(node, HTMLIFrameElement) &&
|
||||
(this.options.captureIFrames || node.getAttribute("data-openreplay-capture"))
|
||||
((this.options.captureIFrames && !hasOpenreplayAttribute(node, "obscured"))
|
||||
|| hasOpenreplayAttribute(node, "capture"))
|
||||
) {
|
||||
this.handleIframe(node)
|
||||
}
|
||||
|
|
@ -42,26 +43,25 @@ export default class TopObserver extends Observer {
|
|||
|
||||
private iframeObservers: IFrameObserver[] = [];
|
||||
private handleIframe(iframe: HTMLIFrameElement): void {
|
||||
let context: Window | null = null
|
||||
let doc: Document | null = null
|
||||
const handle = this.app.safe(() => {
|
||||
const id = this.app.nodes.getID(iframe)
|
||||
if (id === undefined) { return } //log
|
||||
if (iframe.contentWindow === context) { return } //Does this happen frequently?
|
||||
context = iframe.contentWindow as Window | null;
|
||||
if (!context) { return }
|
||||
const observer = new IFrameObserver(this.app, context)
|
||||
if (iframe.contentDocument === doc) { return } // How frequently can it happen?
|
||||
doc = iframe.contentDocument
|
||||
if (!doc || !iframe.contentWindow) { return }
|
||||
const observer = new IFrameObserver(this.app)
|
||||
|
||||
this.iframeObservers.push(observer)
|
||||
observer.observe(iframe)
|
||||
})
|
||||
this.app.attachEventListener(iframe, "load", handle)
|
||||
iframe.addEventListener("load", handle) // why app.attachEventListener not working?
|
||||
handle()
|
||||
}
|
||||
|
||||
private shadowRootObservers: ShadowRootObserver[] = []
|
||||
private handleShadowRoot(shRoot: ShadowRoot) {
|
||||
const observer = new ShadowRootObserver(this.app, this.context)
|
||||
|
||||
const observer = new ShadowRootObserver(this.app)
|
||||
this.shadowRootObservers.push(observer)
|
||||
observer.observe(shRoot.host)
|
||||
}
|
||||
|
|
@ -81,9 +81,9 @@ export default class TopObserver extends Observer {
|
|||
// the change in the re-player behaviour caused by CreateDocument message:
|
||||
// the 0-node ("fRoot") will become #document rather than documentElement as it is now.
|
||||
// Alternatively - observe(#document) then bindNode(documentElement)
|
||||
this.observeRoot(this.context.document, () => {
|
||||
this.observeRoot(window.document, () => {
|
||||
this.app.send(new CreateDocument())
|
||||
}, this.context.document.documentElement);
|
||||
}, window.document.documentElement);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue