tracker: fix maintainer doc node stability and build process
This commit is contained in:
parent
70deccdcfc
commit
e7afd66820
18 changed files with 159 additions and 91 deletions
|
|
@ -251,4 +251,8 @@ export default class APIClient {
|
|||
this.init.method = 'PATCH';
|
||||
return this.fetch(path, params, 'PATCH');
|
||||
}
|
||||
|
||||
forceSiteId = (siteId: string) => {
|
||||
this.siteId = siteId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,15 @@ function isStyleVElement(vElem: VElement): vElem is VElement & { node: StyleElem
|
|||
return vElem.tagName.toLowerCase() === "style"
|
||||
}
|
||||
|
||||
function setupWindowLogging(vTexts: Map<number, VText>, vElements: Map<number, VElement>, olVRoots: Map<number, OnloadVRoot>) {
|
||||
// @ts-ignore
|
||||
window.checkVElements = () => vElements
|
||||
// @ts-ignore
|
||||
window.checkVTexts = () => vTexts
|
||||
// @ts-ignore
|
||||
window.checkVRoots = () => olVRoots
|
||||
}
|
||||
|
||||
const IGNORED_ATTRS = [ "autocomplete" ]
|
||||
const ATTR_NAME_REGEXP = /([^\t\n\f \/>"'=]+)/
|
||||
|
||||
|
|
@ -56,6 +65,11 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
super()
|
||||
this.selectionManager = new SelectionManager(this.vElements, screen)
|
||||
this.stylesManager = new StylesManager(screen, setCssLoading)
|
||||
setupWindowLogging(
|
||||
this.vTexts,
|
||||
this.vElements,
|
||||
this.olVRoots,
|
||||
)
|
||||
}
|
||||
|
||||
setStringDict(stringDict: Record<number,string>) {
|
||||
|
|
@ -216,7 +230,7 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
}
|
||||
case MType.CreateElementNode: {
|
||||
// if (msg.tag.toLowerCase() === 'canvas') msg.tag = 'video'
|
||||
const vElem = new VElement(msg.tag, msg.svg, msg.index)
|
||||
const vElem = new VElement(msg.tag, msg.svg, msg.index, msg.id)
|
||||
if (['STYLE', 'style', 'LINK'].includes(msg.tag)) {
|
||||
vElem.prioritized = true
|
||||
}
|
||||
|
|
@ -296,7 +310,7 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
return
|
||||
}
|
||||
|
||||
/** @deprecated
|
||||
/** @deprecated
|
||||
* since 4.0.2 in favor of AdoptedSsInsertRule/DeleteRule + AdoptedSsAddOwner as a common case for StyleSheets
|
||||
*/
|
||||
case MType.CssInsertRule: {
|
||||
|
|
@ -422,11 +436,11 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Moves and applies all the messages from the current (or from the beginning, if t < current.time)
|
||||
* Moves and applies all the messages from the current (or from the beginning, if t < current.time)
|
||||
* to the one with msg[time] >= `t`
|
||||
*
|
||||
*
|
||||
* This function autoresets pointer if necessary (better name?)
|
||||
*
|
||||
*
|
||||
* @returns Promise that fulfills when necessary changes get applied
|
||||
* (the async part exists mostly due to styles loading)
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -144,12 +144,14 @@ export class VElement extends VParent<Element> {
|
|||
parentNode: VParent | null = null /** Should be modified only by he parent itself */
|
||||
private newAttributes: Map<string, string | false> = new Map()
|
||||
|
||||
constructor(readonly tagName: string, readonly isSVG = false, public readonly index: number) { super() }
|
||||
constructor(readonly tagName: string, readonly isSVG = false, public readonly index: number, private readonly nodeId: number) { super() }
|
||||
protected createNode() {
|
||||
try {
|
||||
return this.isSVG
|
||||
const element = this.isSVG
|
||||
? document.createElementNS('http://www.w3.org/2000/svg', this.tagName)
|
||||
: document.createElement(this.tagName)
|
||||
element.dataset['openreplayId'] = this.nodeId.toString()
|
||||
return element
|
||||
} catch (e) {
|
||||
console.error('Openreplay: Player received invalid html tag', this.tagName, e)
|
||||
return document.createElement(this.tagName.replace(/[^a-z]/gi, ''))
|
||||
|
|
|
|||
|
|
@ -1,80 +1,89 @@
|
|||
import APIClient from 'App/api_client';
|
||||
|
||||
const ALLOWED_404 = "No-file-and-this-is-ok"
|
||||
const NO_BACKUP_FILE = "No-efs-file"
|
||||
export const NO_URLS = 'No-urls-provided'
|
||||
|
||||
const ALLOWED_404 = 'No-file-and-this-is-ok';
|
||||
const NO_BACKUP_FILE = 'No-efs-file';
|
||||
export const NO_URLS = 'No-urls-provided';
|
||||
|
||||
export async function loadFiles(
|
||||
urls: string[],
|
||||
onData: (data: Uint8Array) => void,
|
||||
canSkip: boolean = false,
|
||||
canSkip: boolean = false
|
||||
): Promise<void> {
|
||||
if (!urls.length) {
|
||||
throw NO_URLS
|
||||
throw NO_URLS;
|
||||
}
|
||||
try {
|
||||
for (let url of urls) {
|
||||
await loadFile(url, onData, urls.length > 1 ? url !== urls[0] : canSkip)
|
||||
await loadFile(url, onData, urls.length > 1 ? url !== urls[0] : canSkip);
|
||||
}
|
||||
return Promise.resolve()
|
||||
return Promise.resolve();
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadFile(
|
||||
url: string,
|
||||
onData: (data: Uint8Array) => void,
|
||||
canSkip: boolean = false,
|
||||
canSkip: boolean = false
|
||||
): Promise<void> {
|
||||
return window.fetch(url)
|
||||
.then(response => processAPIStreamResponse(response, canSkip))
|
||||
.then(data => onData(data))
|
||||
.catch(e => {
|
||||
return window
|
||||
.fetch(url)
|
||||
.then((response) => processAPIStreamResponse(response, canSkip))
|
||||
.then((data) => onData(data))
|
||||
.catch((e) => {
|
||||
if (e === ALLOWED_404) {
|
||||
return;
|
||||
} else {
|
||||
throw e
|
||||
throw e;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export async function requestEFSDom(sessionId: string) {
|
||||
return await requestEFSMobFile(sessionId + "/dom.mob")
|
||||
return await requestEFSMobFile(sessionId + '/dom.mob');
|
||||
}
|
||||
|
||||
export async function requestEFSDevtools(sessionId: string) {
|
||||
return await requestEFSMobFile(sessionId + "/devtools.mob")
|
||||
return await requestEFSMobFile(sessionId + '/devtools.mob');
|
||||
}
|
||||
|
||||
export async function requestTarball(url: string) {
|
||||
const res = await window.fetch(url)
|
||||
const res = await window.fetch(url);
|
||||
if (res.ok) {
|
||||
const buf = await res.arrayBuffer()
|
||||
return new Uint8Array(buf)
|
||||
const buf = await res.arrayBuffer();
|
||||
return new Uint8Array(buf);
|
||||
} else {
|
||||
throw new Error(res.status.toString())
|
||||
throw new Error(res.status.toString());
|
||||
}
|
||||
}
|
||||
|
||||
async function requestEFSMobFile(filename: string) {
|
||||
const api = new APIClient()
|
||||
const res = await api.fetch('/unprocessed/' + filename)
|
||||
if (res.status >= 400) {
|
||||
throw NO_BACKUP_FILE
|
||||
const api = new APIClient();
|
||||
const siteId = document.location.href.match(
|
||||
/https:\/\/[a-z.-]+\/([a-z0-9]+)\/[a-z-]+\/[0-9]+/
|
||||
)?.[1];
|
||||
if (siteId) {
|
||||
api.forceSiteId(siteId);
|
||||
api.setSiteIdCheck(() => siteId);
|
||||
}
|
||||
return await processAPIStreamResponse(res, false)
|
||||
const res = await api.fetch('/unprocessed/' + filename);
|
||||
if (res.status >= 400) {
|
||||
throw NO_BACKUP_FILE;
|
||||
}
|
||||
return await processAPIStreamResponse(res, false);
|
||||
}
|
||||
|
||||
const processAPIStreamResponse = (response: Response, skippable: boolean) => {
|
||||
return new Promise<ArrayBuffer>((res, rej) => {
|
||||
if (response.status === 404 && skippable) {
|
||||
return rej(ALLOWED_404)
|
||||
return rej(ALLOWED_404);
|
||||
}
|
||||
if (response.status >= 400) {
|
||||
return rej(`Bad file status code ${response.status}. Url: ${response.url}`)
|
||||
return rej(
|
||||
`Bad file status code ${response.status}. Url: ${response.url}`
|
||||
);
|
||||
}
|
||||
res(response.arrayBuffer())
|
||||
}).then(async buf => new Uint8Array(buf))
|
||||
}
|
||||
res(response.arrayBuffer());
|
||||
}).then(async (buf) => new Uint8Array(buf));
|
||||
};
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
- new webvitals messages source
|
||||
|
||||
## 14.0.11 & .12
|
||||
|
||||
- fix for node maintainer stability around `#document` nodes (mainly iframes field)
|
||||
|
||||
## 14.0.10
|
||||
|
||||
- adjust timestamps for messages from tracker instances inside child iframes (if they were loaded later)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@openreplay/tracker",
|
||||
"description": "The OpenReplay tracker main package",
|
||||
"version": "15.0.0",
|
||||
"version": "15.0.1-32",
|
||||
"keywords": [
|
||||
"logging",
|
||||
"replay"
|
||||
|
|
@ -50,19 +50,19 @@
|
|||
"@rollup/plugin-replace": "^6.0.1",
|
||||
"@rollup/plugin-terser": "0.4.4",
|
||||
"@rollup/plugin-typescript": "^12.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.0",
|
||||
"@typescript-eslint/parser": "^5.30.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.10.0",
|
||||
"@typescript-eslint/parser": "^8.10.0",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"jest": "^29.3.1",
|
||||
"jest-environment-jsdom": "^29.3.1",
|
||||
"lint-staged": "^13.0.3",
|
||||
"prettier": "^3.0.3",
|
||||
"replace-in-files": "^2.0.3",
|
||||
"rollup": "^4.1.4",
|
||||
"semver": "^6.3.0",
|
||||
"ts-jest": "^29.0.3",
|
||||
"tslib": "^2.8.0",
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
@ -76,13 +76,5 @@
|
|||
"engines": {
|
||||
"node": ">=14.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,mjs,jsx,ts,tsx}": [
|
||||
"eslint --fix --quiet"
|
||||
],
|
||||
"*.{json,md,html,js,jsx,ts,tsx}": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"packageManager": "yarn@4.5.0"
|
||||
"packageManager": "yarn@4.5.1"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ async function buildWebWorker() {
|
|||
inlineDynamicImports: true,
|
||||
})
|
||||
const webWorkerCode = output[0].code
|
||||
console.log('webworker done!')
|
||||
return webWorkerCode.replace(/"/g, '\\"').replace(/\n/g, '')
|
||||
|
||||
console.log('webworker done!', output.length)
|
||||
|
||||
return webWorkerCode
|
||||
}
|
||||
|
|
|
|||
10
tracker/tracker/src/common/messages.gen.d.ts
vendored
10
tracker/tracker/src/common/messages.gen.d.ts
vendored
|
|
@ -74,7 +74,8 @@ export declare const enum Type {
|
|||
TagTrigger = 120,
|
||||
Redux = 121,
|
||||
SetPageLocation = 122,
|
||||
GraphQL = 123
|
||||
GraphQL = 123,
|
||||
WebVitals = 124
|
||||
}
|
||||
export type Timestamp = [
|
||||
Type.Timestamp,
|
||||
|
|
@ -544,5 +545,10 @@ export type GraphQL = [
|
|||
string,
|
||||
number
|
||||
];
|
||||
type Message = Timestamp | SetPageLocationDeprecated | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | NetworkRequestDeprecated | ConsoleLog | PageLoadTiming | PageRenderTiming | CustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | ReduxDeprecated | Vuex | MobX | NgRx | GraphQLDeprecated | PerformanceTrack | StringDict | SetNodeAttributeDict | ResourceTimingDeprecated | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | MouseClickDeprecated | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | JSException | Zustand | BatchMetadata | PartitionedMessage | NetworkRequest | WSChannel | InputChange | SelectionChange | MouseThrashing | UnbindNodes | ResourceTiming | TabChange | TabData | CanvasNode | TagTrigger | Redux | SetPageLocation | GraphQL;
|
||||
export type WebVitals = [
|
||||
Type.WebVitals,
|
||||
string,
|
||||
string
|
||||
];
|
||||
type Message = Timestamp | SetPageLocationDeprecated | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | NetworkRequestDeprecated | ConsoleLog | PageLoadTiming | PageRenderTiming | CustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | ReduxDeprecated | Vuex | MobX | NgRx | GraphQLDeprecated | PerformanceTrack | StringDict | SetNodeAttributeDict | ResourceTimingDeprecated | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | MouseClickDeprecated | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | JSException | Zustand | BatchMetadata | PartitionedMessage | NetworkRequest | WSChannel | InputChange | SelectionChange | MouseThrashing | UnbindNodes | ResourceTiming | TabChange | TabData | CanvasNode | TagTrigger | Redux | SetPageLocation | GraphQL | WebVitals;
|
||||
export default Message;
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -20,7 +20,7 @@ interface Options {
|
|||
|
||||
class CanvasRecorder {
|
||||
private snapshots: Record<number, CanvasSnapshot> = {}
|
||||
private readonly intervals: NodeJS.Timeout[] = []
|
||||
private readonly intervals: ReturnType<typeof setInterval>[] = []
|
||||
private readonly interval: number
|
||||
private readonly fileExt: 'webp' | 'png' | 'jpeg' | 'avif'
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,11 @@ interface OnStartInfo {
|
|||
userUUID: string
|
||||
}
|
||||
|
||||
/**
|
||||
* this value is injected during build time via rollup
|
||||
* */
|
||||
// @ts-ignore
|
||||
const workerBodyFn = WEBWORKER_BODY
|
||||
const CANCELED = 'canceled' as const
|
||||
const uxtStorageKey = 'or_uxt_active'
|
||||
const bufferStorageKey = 'or_buffer_1'
|
||||
|
|
@ -657,7 +662,7 @@ export default class App {
|
|||
},
|
||||
this.options.crossdomain?.parentDomain ?? '*',
|
||||
)
|
||||
console.log('Trying to signal to parent, attempt:', retries + 1)
|
||||
this.debug.info('Trying to signal to parent, attempt:', retries + 1)
|
||||
retries++
|
||||
}
|
||||
|
||||
|
|
@ -693,6 +698,7 @@ export default class App {
|
|||
this.pageFrames = pageIframes
|
||||
targetFrame = pageIframes.find((frame) => frame.contentWindow === source)
|
||||
}
|
||||
|
||||
if (!targetFrame) {
|
||||
return null
|
||||
}
|
||||
|
|
@ -721,7 +727,7 @@ export default class App {
|
|||
private initWorker() {
|
||||
try {
|
||||
this.worker = new Worker(
|
||||
URL.createObjectURL(new Blob(['WEBWORKER_BODY'], { type: 'text/javascript' })),
|
||||
URL.createObjectURL(new Blob([workerBodyFn], { type: 'text/javascript' })),
|
||||
)
|
||||
this.worker.onerror = (e) => {
|
||||
this._debug('webworker_error', e)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,13 @@ export default class Logger {
|
|||
return this.level >= level
|
||||
}
|
||||
|
||||
info = (...args: any[]) => {
|
||||
if (this.shouldLog(LogLevel.Verbose)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
console.info(...args)
|
||||
}
|
||||
}
|
||||
|
||||
log = (...args: any[]) => {
|
||||
if (this.shouldLog(LogLevel.Log)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
|
|
|
|||
|
|
@ -30,30 +30,32 @@ function processMapInBatches(
|
|||
processNextBatch()
|
||||
}
|
||||
|
||||
function isNodeStillActive(node: Node): boolean {
|
||||
function isNodeStillActive(node: Node): [isCon: boolean, reason: string] {
|
||||
try {
|
||||
if (!node.isConnected) {
|
||||
return false
|
||||
return [false, 'not connected']
|
||||
}
|
||||
const nodeIsDocument = node.nodeType === Node.DOCUMENT_NODE
|
||||
const nodeWindow = nodeIsDocument
|
||||
? (node as Document).defaultView
|
||||
: node.ownerDocument?.defaultView
|
||||
|
||||
const nodeWindow = node.ownerDocument?.defaultView
|
||||
|
||||
const ownerDoc = nodeIsDocument ? (node as Document) : node.ownerDocument
|
||||
if (!nodeWindow) {
|
||||
return false
|
||||
return [false, 'no window']
|
||||
}
|
||||
|
||||
if (nodeWindow.closed) {
|
||||
return false
|
||||
return [false, 'window closed']
|
||||
}
|
||||
|
||||
if (!node.ownerDocument.documentElement.isConnected) {
|
||||
return false
|
||||
if (!ownerDoc?.documentElement.isConnected) {
|
||||
return [false, 'documentElement not connected']
|
||||
}
|
||||
|
||||
return true
|
||||
return [true, 'ok']
|
||||
} catch (e) {
|
||||
console.error('Error checking node activity:', e)
|
||||
return false
|
||||
return [false, e]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +104,8 @@ class Maintainer {
|
|||
|
||||
this.interval = setInterval(() => {
|
||||
processMapInBatches(this.nodes, this.options.batchSize, (node) => {
|
||||
if (!isNodeStillActive(node)) {
|
||||
const isActive = isNodeStillActive(node)[0]
|
||||
if (!isActive) {
|
||||
this.unregisterNode(node)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import Observer from './observer.js'
|
||||
import { CreateIFrameDocument } from '../messages.gen.js'
|
||||
import { CreateIFrameDocument, RemoveNode } from '../messages.gen.js'
|
||||
|
||||
export default class IFrameObserver extends Observer {
|
||||
docId: number | undefined
|
||||
observe(iframe: HTMLIFrameElement) {
|
||||
const doc = iframe.contentDocument
|
||||
const hostID = this.app.nodes.getID(iframe)
|
||||
if (!doc || hostID === undefined) {
|
||||
return
|
||||
} //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)
|
||||
|
|
@ -15,6 +16,7 @@ export default class IFrameObserver extends Observer {
|
|||
this.app.debug.log('OpenReplay: Iframe document not bound')
|
||||
return
|
||||
}
|
||||
this.docId = docID
|
||||
this.app.send(CreateIFrameDocument(hostID, docID))
|
||||
})
|
||||
}
|
||||
|
|
@ -28,4 +30,11 @@ export default class IFrameObserver extends Observer {
|
|||
this.app.send(CreateIFrameDocument(rootNodeId, docID))
|
||||
})
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this.docId !== undefined) {
|
||||
this.app.send(RemoveNode(this.docId))
|
||||
}
|
||||
super.disconnect()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ export default class TopObserver extends Observer {
|
|||
},
|
||||
params.options,
|
||||
)
|
||||
|
||||
// IFrames
|
||||
this.app.nodes.attachNodeCallback((node) => {
|
||||
if (
|
||||
|
|
@ -64,25 +63,34 @@ export default class TopObserver extends Observer {
|
|||
return this.iframeOffsets.getDocumentOffset(doc)
|
||||
}
|
||||
|
||||
private iframeObserversArr: IFrameObserver[] = []
|
||||
private iframeObservers: WeakMap<HTMLIFrameElement | Document, IFrameObserver> = new WeakMap()
|
||||
private docObservers: WeakMap<Document, IFrameObserver> = new WeakMap()
|
||||
private handleIframe(iframe: HTMLIFrameElement): void {
|
||||
let doc: Document | null = null
|
||||
// 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?))
|
||||
// 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 || !canAccessIframe(iframe)) return
|
||||
const currentWin = iframe.contentWindow
|
||||
const currentDoc = iframe.contentDocument
|
||||
if (currentDoc && currentDoc !== doc) {
|
||||
const observer = new IFrameObserver(this.app)
|
||||
this.iframeObservers.set(iframe, observer)
|
||||
observer.observe(iframe) // TODO: call unregisterNode for the previous doc if present (incapsulate: one iframe - one observer)
|
||||
doc = currentDoc
|
||||
|
||||
this.iframeOffsets.observe(iframe)
|
||||
if (!currentDoc) {
|
||||
this.app.debug.warn('no doc for iframe found', iframe)
|
||||
return
|
||||
}
|
||||
if (currentDoc && this.docObservers.has(currentDoc)) {
|
||||
this.app.debug.info('doc already observed for', id)
|
||||
return
|
||||
}
|
||||
const observer = new IFrameObserver(this.app)
|
||||
this.iframeObservers.set(iframe, observer)
|
||||
this.docObservers.set(currentDoc, observer)
|
||||
this.iframeObserversArr.push(observer)
|
||||
|
||||
observer.observe(iframe)
|
||||
|
||||
this.iframeOffsets.observe(iframe)
|
||||
if (
|
||||
currentWin &&
|
||||
// Sometimes currentWin.window is null (not in specification). Such window object is not functional
|
||||
|
|
@ -91,13 +99,13 @@ export default class TopObserver extends Observer {
|
|||
//TODO: more explicit logic
|
||||
) {
|
||||
this.contextsSet.add(currentWin)
|
||||
//@ts-ignore https://github.com/microsoft/TypeScript/issues/41684
|
||||
// @ts-ignore https://github.com/microsoft/TypeScript/issues/41684
|
||||
this.contextCallbacks.forEach((cb) => cb(currentWin))
|
||||
}
|
||||
// we need this delay because few iframes stacked one in another with rapid updates will break the player (or browser engine rather?)
|
||||
}, 100),
|
||||
}, 250),
|
||||
)
|
||||
iframe.addEventListener('load', handle) // why app.attachEventListener not working?
|
||||
iframe.addEventListener('load', handle)
|
||||
handle()
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +126,6 @@ export default class TopObserver extends Observer {
|
|||
observer.handleShadowRoot(shadow)
|
||||
return shadow
|
||||
}
|
||||
|
||||
this.app.nodes.clear()
|
||||
// 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>
|
||||
|
|
@ -155,8 +162,11 @@ export default class TopObserver extends Observer {
|
|||
disconnect() {
|
||||
this.iframeOffsets.clear()
|
||||
Element.prototype.attachShadow = attachShadowNativeFn
|
||||
this.iframeObserversArr.forEach((observer) => observer.disconnect())
|
||||
this.iframeObserversArr = []
|
||||
this.iframeObservers = new WeakMap()
|
||||
this.shadowRootObservers = new WeakMap()
|
||||
this.docObservers = new WeakMap()
|
||||
super.disconnect()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ export default function (app: App, options?: MouseHandlerOptions): void {
|
|||
let direction = 0
|
||||
let directionChangeCount = 0
|
||||
let distance = 0
|
||||
let checkIntervalId: NodeJS.Timer
|
||||
let checkIntervalId: ReturnType<typeof setInterval>
|
||||
const shakeThreshold = 0.008
|
||||
const shakeCheckInterval = 225
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"extends": "../../tsconfig-base.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["es6", "webworker"],
|
||||
"lib": ["ES2020", "webworker"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"target": "es2016",
|
||||
"preserveConstEnums": false,
|
||||
"declaration": false
|
||||
},
|
||||
"references": [{ "path": "../common" }]
|
||||
"references": [{ "path": "../../common" }]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue