From edc5d93b63a06436e6a6333ef019e0958f68069f Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Fri, 12 May 2023 17:42:10 +0200 Subject: [PATCH] feat(player): basic tabchange event support --- frontend/app/components/Session_/Subheader.js | 7 + frontend/app/player/common/ListWalker.ts | 26 +- frontend/app/player/web/MessageLoader.ts | 4 + frontend/app/player/web/MessageManager.ts | 243 +++++++----------- frontend/app/player/web/TabManager.ts | 90 +++++-- frontend/app/player/web/WebPlayer.ts | 3 +- .../player/web/managers/ActiveTabManager.ts | 18 ++ .../app/player/web/messages/MFileReader.ts | 6 + .../web/messages/RawMessageReader.gen.ts | 2 - 9 files changed, 223 insertions(+), 176 deletions(-) create mode 100644 frontend/app/player/web/managers/ActiveTabManager.ts diff --git a/frontend/app/components/Session_/Subheader.js b/frontend/app/components/Session_/Subheader.js index 160131728..2f6a84cf8 100644 --- a/frontend/app/components/Session_/Subheader.js +++ b/frontend/app/components/Session_/Subheader.js @@ -30,6 +30,8 @@ function SubHeader(props) { exceptionsList, eventList: eventsList, endTime, + currentTab, + tabs, } = store.get(); const enabledIntegration = useMemo(() => { @@ -114,6 +116,11 @@ function SubHeader(props) { )} + {tabs.map((tab, i) => ( +
+ Tab {i+1} +
+ ))}
{ /** * @returns last message with the time <= t. * Assumed that the current message is already handled so - * if pointer doesn't cahnge is returned. + * if pointer doesn't change is returned. */ moveGetLast(t: number, index?: number): T | null { let key: string = "time"; //TODO @@ -130,6 +130,30 @@ export default class ListWalker { return changed ? this.list[ this.p - 1 ] : null; } + moveGetLastDebug(t: number, index?: number): T | null { + let key: string = "time"; //TODO + let val = t; + if (index) { + key = "_index"; + val = index; + } + + let changed = false; + while (this.p < this.length && this.list[this.p][key] <= val) { + this.moveNext() + changed = true; + } + while (this.p > 0 && this.list[ this.p - 1 ][key] > val) { + this.movePrev() + changed = true; + } + + console.log(this.list[this.p - 1]) + return changed ? this.list[ this.p - 1 ] : null; + } + + + /** * Moves over the messages starting from the current+1 to the last one with the time <= t * applying callback on each of them diff --git a/frontend/app/player/web/MessageLoader.ts b/frontend/app/player/web/MessageLoader.ts index 4e35aff91..9c30d1890 100644 --- a/frontend/app/player/web/MessageLoader.ts +++ b/frontend/app/player/web/MessageLoader.ts @@ -29,6 +29,7 @@ export default class MessageLoader { private store: Store, private messageManager: MessageManager, private isClickmap: boolean, + private uiErrorHandler?: { error: (msg: string) => void } ) {} createNewParser(shouldDecrypt = true, file?: string, toggleStatus?: (isLoading: boolean) => void) { @@ -57,6 +58,9 @@ export default class MessageLoader { this.messageManager._sortMessagesHack(sorted) toggleStatus?.(false); this.messageManager.setMessagesLoading(false) + }).catch(e => { + console.error(e) + this.uiErrorHandler?.error('Error parsing file: ' + e.message) }) } diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index 82c294875..0d73c3076 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -3,12 +3,6 @@ import { Decoder } from "syncod"; import logger from 'App/logger'; import { TYPES as EVENT_TYPES } from 'Types/session/event'; -import { Log } from 'Player'; -import { - ResourceType, - getResourceFromResourceTiming, - getResourceFromNetworkRequest -} from 'Player' import type { Store } from 'Player'; import ListWalker from '../common/ListWalker'; @@ -21,7 +15,6 @@ import WindowNodeCounter from './managers/WindowNodeCounter'; import ActivityManager from './managers/ActivityManager'; import { MouseThrashing, MType } from "./messages"; -import { isDOMType } from './messages/filters.gen'; import type { Message, SetPageLocation, @@ -41,6 +34,8 @@ import Screen, { import type { InitialLists } from './Lists' import type { PerformanceChartPoint } from './managers/PerformanceTrackManager'; import type { SkipInterval } from './managers/ActivityManager'; +import TabManager from "Player/web/TabManager"; +import ActiveTabManager from "Player/web/managers/ActiveTabManager"; export interface State extends ScreenState, ListsState { performanceChartData: PerformanceChartPoint[], @@ -62,10 +57,12 @@ export interface State extends ScreenState, ListsState { lastMessageTime: number, firstVisualEvent: number, messagesProcessed: boolean, + currentTab: string, + tabs: string[], } -const visualChanges = [ +export const visualChanges = [ MType.MouseMove, MType.MouseClick, MType.CreateElementNode, @@ -88,6 +85,8 @@ export default class MessageManager { firstVisualEvent: 0, messagesProcessed: false, messagesLoading: false, + currentTab: '', + tabs: [], } private locationEventManager: ListWalker/**/ = new ListWalker(); @@ -114,12 +113,14 @@ export default class MessageManager { private navigationStartOffset: number = 0; private lastMessageTime: number = 0; private firstVisualEventSet = false; + private tabs: Record = {}; + private activeTabManager = new ActiveTabManager() constructor( private readonly session: any /*Session*/, private readonly state: Store, private readonly screen: Screen, - initialLists?: Partial, + private readonly initialLists?: Partial, private readonly uiErrorHandler?: { error: (error: string) => void, }, ) { this.pagesManager = new PagesManager(screen, this.session.isMobile, this.setCSSLoading) @@ -234,62 +235,8 @@ export default class MessageManager { this.activityManager = new ActivityManager(this.session.duration.milliseconds); } - move(t: number, index?: number): void { - const stateToUpdate: Partial = {}; - /* == REFACTOR_ME == */ - const lastLoadedLocationMsg = this.loadedLocationManager.moveGetLast(t, index); - if (!!lastLoadedLocationMsg) { - // TODO: page-wise resources list // setListsStartTime(lastLoadedLocationMsg.time) - this.navigationStartOffset = lastLoadedLocationMsg.navigationStart - this.sessionStart; - } - const llEvent = this.locationEventManager.moveGetLast(t, index); - if (!!llEvent) { - if (llEvent.domContentLoadedTime != null) { - stateToUpdate.domContentLoadedTime = { - time: llEvent.domContentLoadedTime + this.navigationStartOffset, //TODO: predefined list of load event for the network tab (merge events & SetPageLocation: add navigationStart to db) - value: llEvent.domContentLoadedTime, - } - } - if (llEvent.loadTime != null) { - stateToUpdate.loadTime = { - time: llEvent.loadTime + this.navigationStartOffset, - value: llEvent.loadTime, - } - } - if (llEvent.domBuildingTime != null) { - stateToUpdate.domBuildingTime = llEvent.domBuildingTime; - } - } - /* === */ - const lastLocationMsg = this.locationManager.moveGetLast(t, index); - if (!!lastLocationMsg) { - stateToUpdate.location = lastLocationMsg.url; - } - const lastConnectionInfoMsg = this.connectionInfoManger.moveGetLast(t, index); - if (!!lastConnectionInfoMsg) { - stateToUpdate.connType = lastConnectionInfoMsg.type; - stateToUpdate.connBandwidth = lastConnectionInfoMsg.downlink; - } - const lastPerformanceTrackMessage = this.performanceTrackManager.moveGetLast(t, index); - if (!!lastPerformanceTrackMessage) { - stateToUpdate.performanceChartTime = lastPerformanceTrackMessage.time; - } - - Object.assign(stateToUpdate, this.lists.moveGetState(t)) - Object.keys(stateToUpdate).length > 0 && this.state.update(stateToUpdate); - - /* Sequence of the managers is important here */ - // Preparing the size of "screen" - const lastResize = this.resizeManager.moveGetLast(t, index); - if (!!lastResize) { - this.setSize(lastResize) - } - this.pagesManager.moveReady(t).then(() => { - - const lastScroll = this.scrollManager.moveGetLast(t, index); - if (!!lastScroll && this.screen.window) { - this.screen.window.scrollTo(lastScroll.x, lastScroll.y); - } + move(t: number): any { + this.activeTabManager.moveReady(t).then(tabId => { // Moving mouse and setting :hover classes on ready view this.mouseMoveManager.move(t); const lastClick = this.clickManager.moveGetLast(t); @@ -300,6 +247,11 @@ export default class MessageManager { if (!!lastThrashing && t - lastThrashing.time < 300) { this.screen.cursor.shake(); } + + if (tabId && this.state.get().currentTab !== tabId) { + this.state.update({ currentTab: tabId }) + } + this.tabs[this.state.get().currentTab].move(t) }) if (this.waitingForFiles && this.lastMessageTime <= t && t !== this.session.duration.milliseconds) { @@ -308,7 +260,21 @@ export default class MessageManager { } - distributeMessage = (msg: Message): void => { + distributeMessage = (msg: Message & { tabId: string }): void => { + if (!this.tabs[msg.tabId]) { + console.log(msg.tabId) + this.tabs[msg.tabId] = new TabManager( + this.session, + this.state, + this.screen, + msg.tabId, + this.setSize, + this.initialLists, + ) + } + + // return this.tabs[msg.tabId].distributeMessage(msg) + const lastMessageTime = Math.max(msg.time, this.lastMessageTime) this.lastMessageTime = lastMessageTime this.state.update({ lastMessageTime }) @@ -316,14 +282,9 @@ export default class MessageManager { this.activityManager?.updateAcctivity(msg.time); } switch (msg.tp) { - case MType.SetPageLocation: - this.locationManager.append(msg); - if (msg.navigationStart > 0) { - this.loadedLocationManager.append(msg); - } - break; - case MType.SetViewportSize: - this.resizeManager.append(msg); + case MType.TabChange: + this.state.update({ tabs: this.state.get().tabs.concat(msg.tabId) }) + this.activeTabManager.append(msg) break; case MType.MouseThrashing: this.mouseThrashingManager.append(msg); @@ -334,89 +295,73 @@ export default class MessageManager { case MType.MouseClick: this.clickManager.append(msg); break; - case MType.SetViewportScroll: - this.scrollManager.append(msg); - break; - case MType.PerformanceTrack: - this.performanceTrackManager.append(msg); - break; - case MType.SetPageVisibility: - this.performanceTrackManager.handleVisibility(msg) - break; - case MType.ConnectionInformation: - this.connectionInfoManger.append(msg); - break; - case MType.OTable: - this.decoder.set(msg.key, msg.value); - break; - /* Lists: */ - case MType.ConsoleLog: - if (msg.level === 'debug') break; - this.lists.lists.log.append( - // @ts-ignore : TODO: enums in the message schema - Log(msg) - ) - break; - case MType.ResourceTimingDeprecated: - case MType.ResourceTiming: - // TODO: merge `resource` and `fetch` lists into one here instead of UI - if (msg.initiator !== ResourceType.FETCH && msg.initiator !== ResourceType.XHR) { - // @ts-ignore TODO: typing for lists - this.lists.lists.resource.insert(getResourceFromResourceTiming(msg, this.sessionStart)) - } - break; - case MType.Fetch: - case MType.NetworkRequest: - this.lists.lists.fetch.insert(getResourceFromNetworkRequest(msg, this.sessionStart)) - break; - case MType.Redux: - this.lists.lists.redux.append(msg); - break; - case MType.NgRx: - this.lists.lists.ngrx.append(msg); - break; - case MType.Vuex: - this.lists.lists.vuex.append(msg); - break; - case MType.Zustand: - this.lists.lists.zustand.append(msg) - break - case MType.MobX: - this.lists.lists.mobx.append(msg); - break; - case MType.GraphQl: - this.lists.lists.graphql.append(msg); - break; - case MType.Profiler: - this.lists.lists.profiles.append(msg); - break; + // /* Lists: */ + // case MType.ConsoleLog: + // if (msg.level === 'debug') break; + // this.lists.lists.log.append( + // // @ts-ignore : TODO: enums in the message schema + // Log(msg) + // ) + // break; + // case MType.ResourceTimingDeprecated: + // case MType.ResourceTiming: + // // TODO: merge `resource` and `fetch` lists into one here instead of UI + // if (msg.initiator !== ResourceType.FETCH && msg.initiator !== ResourceType.XHR) { + // // @ts-ignore TODO: typing for lists + // this.lists.lists.resource.insert(getResourceFromResourceTiming(msg, this.sessionStart)) + // } + // break; + // case MType.Fetch: + // case MType.NetworkRequest: + // this.lists.lists.fetch.insert(getResourceFromNetworkRequest(msg, this.sessionStart)) + // break; + // case MType.Redux: + // this.lists.lists.redux.append(msg); + // break; + // case MType.NgRx: + // this.lists.lists.ngrx.append(msg); + // break; + // case MType.Vuex: + // this.lists.lists.vuex.append(msg); + // break; + // case MType.Zustand: + // this.lists.lists.zustand.append(msg) + // break + // case MType.MobX: + // this.lists.lists.mobx.append(msg); + // break; + // case MType.GraphQl: + // this.lists.lists.graphql.append(msg); + // break; + // case MType.Profiler: + // this.lists.lists.profiles.append(msg); + // break; /* ===|=== */ default: switch (msg.tp) { case MType.CreateDocument: if (!this.firstVisualEventSet) { - this.state.update({ firstVisualEvent: msg.time }); + this.state.update({ firstVisualEvent: msg.time, currentTab: msg.tabId, tabs: [msg.tabId] }); this.firstVisualEventSet = true; } - this.windowNodeCounter.reset(); - this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); - break; - case MType.CreateTextNode: - case MType.CreateElementNode: - this.windowNodeCounter.addNode(msg.id, msg.parentID); - this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); - break; - case MType.MoveNode: - this.windowNodeCounter.moveNode(msg.id, msg.parentID); - this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); - break; - case MType.RemoveNode: - this.windowNodeCounter.removeNode(msg.id); - this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); - break; + // break; + // case MType.CreateTextNode: + // case MType.CreateElementNode: + // this.windowNodeCounter.addNode(msg.id, msg.parentID); + // this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); + // break; + // case MType.MoveNode: + // this.windowNodeCounter.moveNode(msg.id, msg.parentID); + // this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); + // break; + // case MType.RemoveNode: + // this.windowNodeCounter.removeNode(msg.id); + // this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); + // break; } - this.performanceTrackManager.addNodeCountPointIfNeed(msg.time) - isDOMType(msg.tp) && this.pagesManager.appendMessage(msg) + this.tabs[msg.tabId].distributeMessage(msg) + // this.performanceTrackManager.addNodeCountPointIfNeed(msg.time) + // isDOMType(msg.tp) && this.pagesManager.appendMessage(msg) break; } } diff --git a/frontend/app/player/web/TabManager.ts b/frontend/app/player/web/TabManager.ts index f9b5ca59b..2890d67a6 100644 --- a/frontend/app/player/web/TabManager.ts +++ b/frontend/app/player/web/TabManager.ts @@ -18,6 +18,7 @@ import MouseMoveManager from "Player/web/managers/MouseMoveManager"; import ActivityManager from "Player/web/managers/ActivityManager"; import { getResourceFromNetworkRequest, getResourceFromResourceTiming, Log, ResourceType } from "Player"; import { isDOMType } from "Player/web/messages/filters.gen"; +import { State, visualChanges } from './MessageManager' export default class TabManager { private locationEventManager: ListWalker/**/ = new ListWalker(); @@ -39,7 +40,9 @@ export default class TabManager { private readonly session: any, private readonly state: Store<{}>, private readonly screen: Screen, - initialLists?: Partial + private readonly id: string, + private readonly setSize: ({ height, width }: { height: number, width: number }) => void, + initialLists?: Partial, ) { this.pagesManager = new PagesManager(screen, this.session.isMobile, this.setCSSLoading) this.lists = new Lists(initialLists) @@ -84,15 +87,6 @@ export default class TabManager { distributeMessage(msg: Message): void { - const lastMessageTime = Math.max(msg.time, this.lastMessageTime) - this.lastMessageTime = lastMessageTime - this.state.update({ lastMessageTime }) - if (visualChanges.includes(msg.tp)) { - this.activityManager?.updateAcctivity(msg.time); - } - let decoded; - const time = msg.time; - // console.log(msg) switch (msg.tp) { case MType.SetPageLocation: this.locationManager.append(msg); @@ -103,15 +97,6 @@ export default class TabManager { case MType.SetViewportSize: this.resizeManager.append(msg); break; - case MType.MouseThrashing: - this.mouseThrashingManager.append(msg); - break; - case MType.MouseMove: - this.mouseMoveManager.append(msg); - break; - case MType.MouseClick: - this.clickManager.append(msg); - break; case MType.SetViewportScroll: this.scrollManager.append(msg); break; @@ -171,10 +156,6 @@ export default class TabManager { default: switch (msg.tp) { case MType.CreateDocument: - if (!this.firstVisualEventSet) { - this.state.update({ firstVisualEvent: msg.time }); - this.firstVisualEventSet = true; - } this.windowNodeCounter.reset(); this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); break; @@ -197,4 +178,67 @@ export default class TabManager { break; } } + + move(t: number, index?: number): void { + + const stateToUpdate: Partial = {}; + /* == REFACTOR_ME == */ + const lastLoadedLocationMsg = this.loadedLocationManager.moveGetLast(t, index); + if (!!lastLoadedLocationMsg) { + // TODO: page-wise resources list // setListsStartTime(lastLoadedLocationMsg.time) + this.navigationStartOffset = lastLoadedLocationMsg.navigationStart - this.sessionStart; + } + const llEvent = this.locationEventManager.moveGetLast(t, index); + if (!!llEvent) { + if (llEvent.domContentLoadedTime != null) { + stateToUpdate.domContentLoadedTime = { + time: llEvent.domContentLoadedTime + this.navigationStartOffset, //TODO: predefined list of load event for the network tab (merge events & SetPageLocation: add navigationStart to db) + value: llEvent.domContentLoadedTime, + } + } + if (llEvent.loadTime != null) { + stateToUpdate.loadTime = { + time: llEvent.loadTime + this.navigationStartOffset, + value: llEvent.loadTime, + } + } + if (llEvent.domBuildingTime != null) { + stateToUpdate.domBuildingTime = llEvent.domBuildingTime; + } + } + /* === */ + const lastLocationMsg = this.locationManager.moveGetLast(t, index); + if (!!lastLocationMsg) { + stateToUpdate.location = lastLocationMsg.url; + } + const lastConnectionInfoMsg = this.connectionInfoManger.moveGetLast(t, index); + if (!!lastConnectionInfoMsg) { + stateToUpdate.connType = lastConnectionInfoMsg.type; + stateToUpdate.connBandwidth = lastConnectionInfoMsg.downlink; + } + const lastPerformanceTrackMessage = this.performanceTrackManager.moveGetLast(t, index); + if (!!lastPerformanceTrackMessage) { + stateToUpdate.performanceChartTime = lastPerformanceTrackMessage.time; + } + + Object.assign(stateToUpdate, this.lists.moveGetState(t)) + Object.keys(stateToUpdate).length > 0 && this.state.update(stateToUpdate); + + /* Sequence of the managers is important here */ + // Preparing the size of "screen" + const lastResize = this.resizeManager.moveGetLast(t, index); + if (!!lastResize) { + this.setSize(lastResize) + } + this.pagesManager.moveReady(t).then(() => { + const lastScroll = this.scrollManager.moveGetLast(t, index); + if (!!lastScroll && this.screen.window) { + this.screen.window.scrollTo(lastScroll.x, lastScroll.y); + } + }) + + // if (this.waitingForFiles && this.lastMessageTime <= t && t !== this.session.duration.milliseconds) { + // this.setMessagesLoading(true) + // } + } } \ No newline at end of file diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index 407a69068..4e41e3ec4 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -53,7 +53,8 @@ export default class WebPlayer extends Player { session, wpState, messageManager, - isClickMap + isClickMap, + uiErrorHandler ) super(wpState, messageManager) this.screen = screen diff --git a/frontend/app/player/web/managers/ActiveTabManager.ts b/frontend/app/player/web/managers/ActiveTabManager.ts new file mode 100644 index 000000000..c9024571f --- /dev/null +++ b/frontend/app/player/web/managers/ActiveTabManager.ts @@ -0,0 +1,18 @@ +import ListWalker from '../../common/ListWalker'; +import type { TabChange } from '../messages'; + +export default class ActiveTabManager extends ListWalker { + currentTime = 0; + + moveReady(t: number): Promise { + + if (t < this.currentTime) { + this.reset() + } + this.currentTime = t + const msg = this.moveGetLastDebug(t) + console.log('move', t, msg, this.list) + + return Promise.resolve(msg?.tabId || null) + } +} \ No newline at end of file diff --git a/frontend/app/player/web/messages/MFileReader.ts b/frontend/app/player/web/messages/MFileReader.ts index 732ca74be..929fb972d 100644 --- a/frontend/app/player/web/messages/MFileReader.ts +++ b/frontend/app/player/web/messages/MFileReader.ts @@ -63,6 +63,7 @@ export default class MFileReader extends RawMessageReader { } } + currentTab = '' readNext(): Message & { _index?: number } | null { if (this.error || !this.hasNextByte()) { return null @@ -82,6 +83,10 @@ export default class MFileReader extends RawMessageReader { return null } + if (rMsg.tp === MType.TabData) { + this.currentTab = rMsg.tabId + return this.readNext() + } if (rMsg.tp === MType.Timestamp) { if (!this.startTime) { this.startTime = rMsg.timestamp @@ -93,6 +98,7 @@ export default class MFileReader extends RawMessageReader { const index = this.noIndexes ? 0 : this.getLastMessageID() const msg = Object.assign(rewriteMessage(rMsg), { time: this.currentTime, + tabId: this.currentTab, }, !this.noIndexes ? { _index: index } : {}) return msg diff --git a/frontend/app/player/web/messages/RawMessageReader.gen.ts b/frontend/app/player/web/messages/RawMessageReader.gen.ts index 3a18d019b..0fccc5564 100644 --- a/frontend/app/player/web/messages/RawMessageReader.gen.ts +++ b/frontend/app/player/web/messages/RawMessageReader.gen.ts @@ -674,7 +674,6 @@ export default class RawMessageReader extends PrimitiveReader { } case 118: { - console.log('TabChange') const tabId = this.readString(); if (tabId === null) { return resetPointer() } return { tp: MType.TabChange, @@ -683,7 +682,6 @@ export default class RawMessageReader extends PrimitiveReader { } case 119: { - console.log('TabData') const tabId = this.readString(); if (tabId === null) { return resetPointer() } return { tp: MType.TabData,