diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx index 16b3d54ee..8111f11af 100644 --- a/frontend/app/components/Session/WebPlayer.tsx +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -66,7 +66,7 @@ function WebPlayer(props: any) { React.useEffect(() => { if (session.events.length > 0 || session.errors.length > 0) { - contextValue.player.updateLists(session) + contextValue.player?.updateLists?.(session) } }, [session.events, session.errors]) diff --git a/frontend/app/player/common/types.ts b/frontend/app/player/common/types.ts index 308ec0659..7df4f6f6b 100644 --- a/frontend/app/player/common/types.ts +++ b/frontend/app/player/common/types.ts @@ -7,7 +7,7 @@ export interface Indexed { } export interface Moveable { - move(time: number): void + move(time: number, isJump?: boolean): void } export interface Cleanable { diff --git a/frontend/app/player/player/Animator.ts b/frontend/app/player/player/Animator.ts index e60b28d78..9423b5785 100644 --- a/frontend/app/player/player/Animator.ts +++ b/frontend/app/player/player/Animator.ts @@ -60,12 +60,12 @@ export default class Animator { window.playerJump = this.jump.bind(this) } - private setTime(time: number) { + private setTime(time: number, isJump?: boolean) { this.store.update({ time, completed: false, }) - this.mm.move(time) + this.mm.move(time, isJump) } private startAnimation() { @@ -183,11 +183,11 @@ export default class Animator { jump = (time: number) => { if (this.store.get().playing) { cancelAnimationFrame(this.animationFrameRequestId) - this.setTime(time) + this.setTime(time, true) this.startAnimation() this.store.update({ livePlay: time === this.store.get().endTime }) } else { - this.setTime(time) + this.setTime(time, true) this.store.update({ livePlay: time === this.store.get().endTime }) } } diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index 1fd0e419d..f5916d5a8 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -228,7 +228,7 @@ export default class MessageManager { sorted.forEach(msg => { if (indx > msg._index) outOfOrderCounter++ else indx = msg._index - this.distributeMessage(msg, msg._index) + this.distributeMessage(msg) }) if (outOfOrderCounter > 0) console.warn("Unsorted mob file, error count: ", outOfOrderCounter) @@ -287,7 +287,7 @@ export default class MessageManager { this.activityManager = new ActivityManager(this.session.duration.milliseconds); } - move(t: number, index?: number): void { + move(t: number, isJump?: boolean, index?: number): void { const stateToUpdate: Partial = {}; /* == REFACTOR_ME == */ const lastLoadedLocationMsg = this.loadedLocationManager.moveGetLast(t, index); @@ -337,7 +337,7 @@ export default class MessageManager { if (!!lastResize) { this.setSize(lastResize) } - this.pagesManager.moveReady(t).then(() => { + this.pagesManager.moveReady(t, isJump).then(() => { const lastScroll = this.scrollManager.moveGetLast(t, index); if (!!lastScroll && this.screen.window) { @@ -374,7 +374,7 @@ export default class MessageManager { return { ...msg, ...decoded }; } - distributeMessage(msg: Message, index: number): void { + distributeMessage(msg: Message): void { const lastMessageTime = Math.max(msg.time, this.lastMessageTime) this.lastMessageTime = lastMessageTime this.state.update({ lastMessageTime }) diff --git a/frontend/app/player/web/managers/DOM/DOMManager.ts b/frontend/app/player/web/managers/DOM/DOMManager.ts index 7b773860c..ec51401f6 100644 --- a/frontend/app/player/web/managers/DOM/DOMManager.ts +++ b/frontend/app/player/web/managers/DOM/DOMManager.ts @@ -43,7 +43,7 @@ export default class DOMManager extends ListWalker { private styleSheets: Map = new Map() private ppStyleSheets: Map = new Map() private stringDict: Record = {} - + private attrsBacktrack: Message[] = [] private upperBodyId: number = -1; private nodeScrollManagers: Map> = new Map() @@ -143,6 +143,7 @@ export default class DOMManager extends ListWalker { let { name, value } = msg; const vn = this.vElements.get(msg.id) if (!vn) { logger.error("Node not found", msg); return } + if (vn.node.tagName === "INPUT" && name === "name") { // Otherwise binds local autocomplete values (maybe should ignore on the tracker level) return @@ -152,9 +153,13 @@ export default class DOMManager extends ListWalker { // if (value.startsWith(window.env.ASSETS_HOST || window.location.origin + '/assets')) { // value = value.replace("?", "%3F"); // } - if (!value.startsWith("http")) { return } - // blob:... value happened here. https://foss.openreplay.com/3/session/7013553567419137 - // that resulted in that link being unable to load and having 4sec timeout in the below function. + if (!value.startsWith("http")) { + return + } + // blob:... value can happen here for some reason. + // which will result in that link being unable to load and having 4sec timeout in the below function. + + // TODO: check if node actually exists on the page, not just in memory this.stylesManager.setStyleHandlers(vn.node as HTMLLinkElement, value); } if (vn.node.namespaceURI === 'http://www.w3.org/2000/svg' && value.startsWith("url(")) { @@ -164,8 +169,7 @@ export default class DOMManager extends ListWalker { this.removeBodyScroll(msg.id, vn) } - private applyMessage = (msg: Message): Promise | undefined => { - let node: Node | undefined + private applyMessage = (msg: Message, isJump?: boolean): Promise | undefined => { let vn: VNode | undefined let doc: Document | null let styleSheet: CSSStyleSheet | PostponedStyleSheet | undefined @@ -229,9 +233,12 @@ export default class DOMManager extends ListWalker { if (!vn) { logger.error("Node not found", msg); return } if (!vn.parentNode) { logger.error("Parent node not found", msg); return } vn.parentNode.removeChild(vn) + this.vElements.delete(msg.id) + this.vTexts.delete(msg.id) return case MType.SetNodeAttribute: - this.setNodeAttribute(msg) + if (isJump && msg.name === 'href') this.attrsBacktrack.push(msg) + else this.setNodeAttribute(msg) return case MType.StringDict: this.stringDict[msg.key] = msg.value @@ -240,11 +247,14 @@ export default class DOMManager extends ListWalker { this.stringDict[msg.nameKey] === undefined && logger.error("No dictionary key for msg 'name': ", msg) this.stringDict[msg.valueKey] === undefined && logger.error("No dictionary key for msg 'value': ", msg) if (this.stringDict[msg.nameKey] === undefined || this.stringDict[msg.valueKey] === undefined ) { return } - this.setNodeAttribute({ - id: msg.id, - name: this.stringDict[msg.nameKey], - value: this.stringDict[msg.valueKey], - }) + if (isJump && this.stringDict[msg.nameKey] === 'href') this.attrsBacktrack.push(msg) + else { + this.setNodeAttribute({ + id: msg.id, + name: this.stringDict[msg.nameKey], + value: this.stringDict[msg.valueKey], + }) + } return case MType.RemoveNodeAttribute: vn = this.vElements.get(msg.id) @@ -422,13 +432,53 @@ export default class DOMManager extends ListWalker { } } - async moveReady(t: number): Promise { + applyBacktrack(msg: Message) { + // @ts-ignore + const target = this.vElements.get(msg.id) + if (!target) { + return + } + + switch (msg.tp) { + case MType.SetNodeAttribute: { + this.setNodeAttribute(msg) + return + } + case MType.SetNodeAttributeDict: { + this.stringDict[msg.nameKey] === undefined && logger.error("No dictionary key for msg 'name': ", msg) + this.stringDict[msg.valueKey] === undefined && logger.error("No dictionary key for msg 'value': ", msg) + if (this.stringDict[msg.nameKey] === undefined || this.stringDict[msg.valueKey] === undefined) { + return + } + this.setNodeAttribute({ + id: msg.id, + name: this.stringDict[msg.nameKey], + value: this.stringDict[msg.valueKey], + }) + return; + } + } + } + + async moveReady(t: number, isJump?: boolean): Promise { // MBTODO (back jump optimisation): // - store intemediate virtual dom state // - cancel previous moveReady tasks (is it possible?) if new timestamp is less // This function autoresets pointer if necessary (better name?) - - await this.moveWait(t, this.applyMessage) + + /** + * Basically just skipping all set attribute with attrs being "href" if user is 'jumping' + * to the other point of replay to save time on NOT downloading any resources before the dom tree changes + * are applied, so it won't try to download and then cancel when node is created in msg N and removed in msg N+2 + * which produces weird bug when asset is cached (10-25ms delay) + * */ + await this.moveWait(t, (msg) => this.applyMessage(msg, isJump)) + if (isJump) { + this.attrsBacktrack.forEach(msg => { + this.applyBacktrack(msg) + }) + this.attrsBacktrack = [] + } this.vRoots.forEach(rt => rt.applyChanges()) // MBTODO (optimisation): affected set // Thinkabout (read): css preload diff --git a/frontend/app/player/web/managers/DOM/VirtualDOM.ts b/frontend/app/player/web/managers/DOM/VirtualDOM.ts index 91b75eb24..d9fd3f77a 100644 --- a/frontend/app/player/web/managers/DOM/VirtualDOM.ts +++ b/frontend/app/player/web/managers/DOM/VirtualDOM.ts @@ -108,8 +108,8 @@ export class VElement extends VParent { } else { try { this.node.setAttribute(key, value) - } catch { - // log err + } catch (e) { + console.error(e) } } }) @@ -134,7 +134,8 @@ export class VStyleElement extends VElement { this.stylesheetCallbacks.forEach(cb => cb(sheet)) this.stylesheetCallbacks = [] } else { - console.warn("Style onload: sheet is null") + // console.warn("Style onload: sheet is null") ? + // sometimes logs shit ton of errors for some reason } this.loaded = true } diff --git a/frontend/app/player/web/managers/PagesManager.ts b/frontend/app/player/web/managers/PagesManager.ts index dbc64bb72..b30f40372 100644 --- a/frontend/app/player/web/managers/PagesManager.ts +++ b/frontend/app/player/web/managers/PagesManager.ts @@ -33,14 +33,14 @@ export default class PagesManager extends ListWalker { this.forEach(page => page.sort(comparator)) } - moveReady(t: number): Promise { + moveReady(t: number, isJump?: boolean): Promise { const requiredPage = this.moveGetLast(t) if (requiredPage != null) { this.currentPage = requiredPage this.currentPage.reset() // Otherwise it won't apply create_document } if (this.currentPage != null) { - return this.currentPage.moveReady(t) + return this.currentPage.moveReady(t, isJump) } return Promise.resolve() } diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json index c22de968a..7705d49c9 100644 --- a/tracker/tracker/package.json +++ b/tracker/tracker/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker", "description": "The OpenReplay tracker main package", - "version": "5.0.1", + "version": "5.0.2-beta.2", "keywords": [ "logging", "replay"