From f895c15d11cd13ccbcb3db22ce7a52adf39e73a4 Mon Sep 17 00:00:00 2001 From: Dave Christenson Date: Sat, 11 Nov 2023 20:44:32 -0600 Subject: [PATCH 1/3] Improve scroll performance --- client/src/components/Header.tsx | 4 +- client/src/components/SnapshotTabContent.tsx | 278 +++++++++++++------ client/src/components/SnapshotTabs.tsx | 4 + client/src/store/MessageQueueStore.ts | 21 +- client/src/store/MessageStore.ts | 9 + client/src/store/SnapshotStore.ts | 12 + 6 files changed, 222 insertions(+), 106 deletions(-) diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx index 8496bff4..d93bf34e 100644 --- a/client/src/components/Header.tsx +++ b/client/src/components/Header.tsx @@ -121,7 +121,7 @@ const Header = observer(({ socketStore, messageQueueStore, snapshotStore, filter
{ - messageQueueStore.setScrollToTop(true); + messageQueueStore.setScrollAction('top'); messageQueueStore.setScrollPending(true); }} title={'Scroll to top'} @@ -129,7 +129,7 @@ const Header = observer(({ socketStore, messageQueueStore, snapshotStore, filter
{ - messageQueueStore.setScrollToBottom(true); + messageQueueStore.setScrollAction('bottom'); messageQueueStore.setScrollPending(true); }} title={'Scroll to bottom'} diff --git a/client/src/components/SnapshotTabContent.tsx b/client/src/components/SnapshotTabContent.tsx index 69478f1f..5bf7906c 100644 --- a/client/src/components/SnapshotTabContent.tsx +++ b/client/src/components/SnapshotTabContent.tsx @@ -14,21 +14,25 @@ import JSONFieldButtons from './JSONFieldButtons'; import { snapshotStore } from '../store/SnapshotStore'; import CloseIcon from "@material-ui/icons/Close"; import LayoutStore from '../store/LayoutStore'; +import MessageStore from '../store/MessageStore'; let minEntryHeight = 26; +let lastScrollTime = 0; type Props = { messageQueueStore: MessageQueueStore, selectedReqSeqNum: number, setSelectedReqSeqNum: (seqNum: number) => void, scrollTop: number, - setScrollTop: (scrollTop: number) => void, + setScrollTop: (index: number) => void, + scrollTopIndex: number, + setScrollTopIndex: (index: number) => void, highlightSeqNum: number, setHighlightSeqNum: (seqNum: number) => void, } const SnapshotTabContent = observer(({ - messageQueueStore, selectedReqSeqNum, setSelectedReqSeqNum, scrollTop, setScrollTop, + messageQueueStore, selectedReqSeqNum, setSelectedReqSeqNum, scrollTop, scrollTopIndex, setScrollTop, setScrollTopIndex, highlightSeqNum, setHighlightSeqNum }: Props) => { const [resendStore, setResendStore] = React.useState(); @@ -48,34 +52,90 @@ const SnapshotTabContent = observer(({ setClickPendingSeqNum(Number.MAX_SAFE_INTEGER); } if (messageQueueStore.getScrollPending()) { - if (messageQueueStore.getScrollToTop()) { - if (matchCount > 0) { - let firstSeqNum = 0; - for (const messageStore of messageQueueStore.getMessages()) { - if (!filterStore.isFilteredNoCache(messageStore)) { - firstSeqNum = messageStore.getMessage().sequenceNumber; - break; + if (renderSet.length > 0) { + switch (messageQueueStore.getScrollAction()) { + case 'top': { + let firstSeqNum = 0; + for (let i = 0; i < messageQueueStore.getMessages().length; ++i) { + const messageStore = messageQueueStore.getMessages()[i]; + if (!filterStore.isFilteredNoCache(messageStore)) { + firstSeqNum = messageStore.getMessage().sequenceNumber; + setScrollTopIndex(i); + break; + } } + messageQueueStore.setScrollToSeqNum(firstSeqNum); + setSelectedReqSeqNum(Number.MAX_SAFE_INTEGER); + setHighlightSeqNum(firstSeqNum); + break; } - messageQueueStore.setScrollToSeqNum(firstSeqNum); - setSelectedReqSeqNum(Number.MAX_SAFE_INTEGER); - } - messageQueueStore.setScrollToTop(false); - } - if (messageQueueStore.getScrollToBottom()) { - if (matchCount > 0) { - let lastSeqNum = 0; - messageQueueStore.getMessages() - .forEach(messageStore => { + case 'bottom': { + let lastSeqNum = 0; + let stIndex = 0; + let back = 0; + for (let i = messageQueueStore.getMessages().length - 1; i >= 0; --i) { + const messageStore = messageQueueStore.getMessages()[i]; if (!filterStore.isFilteredNoCache(messageStore)) { - lastSeqNum = messageStore.getMessage().sequenceNumber; + if (lastSeqNum === 0) { + lastSeqNum = messageStore.getMessage().sequenceNumber; + } + if (++back === renderCount) { + break; + } + stIndex = i; } - }); - messageQueueStore.setScrollToSeqNum(lastSeqNum); - setSelectedReqSeqNum(Number.MAX_SAFE_INTEGER); + } + setScrollTopIndex(stIndex); + messageQueueStore.setScrollToSeqNum(lastSeqNum); + setSelectedReqSeqNum(Number.MAX_SAFE_INTEGER); + setHighlightSeqNum(lastSeqNum); + break; + } + case 'pageup': { + let lastSeqNum = 0; + let stIndex = 0; + let back = 0; + for (let i = renderSet[0].getIndex(); i >= 0; --i) { + const messageStore = messageQueueStore.getMessages()[i]; + if (!filterStore.isFilteredNoCache(messageStore)) { + if (lastSeqNum === 0) { + lastSeqNum = messageStore.getMessage().sequenceNumber; + } + if (++back === 4) { + break; + } + stIndex = i; + } + } + if (stIndex !== renderSet[0].getIndex()) { + setScrollTopIndex(stIndex); + messageQueueStore.setScrollToSeqNum(lastSeqNum); + setSelectedReqSeqNum(Number.MAX_SAFE_INTEGER); + setHighlightSeqNum(renderSet[0].getIndex()); + } + break; + } + case 'pagedown': { + let end = renderSet[renderSet.length - 1]; + let more = false; + for (let i = end.getIndex() + 1; i < messageQueueStore.getMessages().length; ++i) { + const messageStore = messageQueueStore.getMessages()[i]; + if (!filterStore.isFilteredNoCache(messageStore)) { + more = true; + break; + } + } + if (more) { + setScrollTopIndex(end.getIndex()); + messageQueueStore.setScrollToSeqNum(end.getMessage().sequenceNumber); + setSelectedReqSeqNum(Number.MAX_SAFE_INTEGER); + setHighlightSeqNum(end.getMessage().sequenceNumber); + } + break; + } } - messageQueueStore.setScrollToBottom(false); } + messageQueueStore.setScrollAction(undefined); messageQueueStore.setScrollPending(false); } }); @@ -96,36 +156,72 @@ const SnapshotTabContent = observer(({ const seqNum = messageQueueStore.getScrollToSeqNum(); messageQueueStore.setScrollToSeqNum(null); if (seqNum !== null) { - doScrollTo(seqNum, 3000); + doScrollTo(seqNum, 0); messageQueueStore.setHighlightSeqNum(seqNum); - setHighlightSeqNum(seqNum); } } } + function calcRenderCount(): number { + let renderCount; + const parent = (requestContainerRef.current as Element); + let height = parent ? parent.clientHeight : window.innerHeight; + height *= 2; + renderCount = height / minEntryHeight; + renderCount = Math.floor(renderCount); + return renderCount; + } + + if (!snapshotStore.isUpdating() || snapshotStore.getUpdatingMessage().length === 0) { + for (const messageStore of messageQueueStore.getMessages()) { + filterStore.isFilteredNoCache(messageStore); + } + } + + let renderCount = calcRenderCount(); + + let renderSet: MessageStore[] = []; let maxStatusSize = 0; let maxMethodSize = 0; let maxEndpointSize = 0; - messageQueueStore.getMessages() - .forEach((messageStore) => { - maxStatusSize = Math.max(maxStatusSize, (messageStore.getMessage().status + '').length); - const method = messageStore.getMessage().method; - maxMethodSize = Math.max(maxMethodSize, method ? method.length : 0); - maxEndpointSize = Math.max(maxEndpointSize, messageStore.getMessage().endpoint.length); - }); + + let activeReqSeqNumAdded = false; + const startIndex = messageQueueStore.getFullPageSearch() ? 0 : scrollTopIndex; + for (let i = startIndex; i < messageQueueStore.getMessages().length; ++i) { + const messageStore = messageQueueStore.getMessages()[i]; + messageStore.setIndex(i); + maxStatusSize = Math.max(maxStatusSize, (messageStore.getMessage().status + '').length); + const method = messageStore.getMessage().method; + maxMethodSize = Math.max(maxMethodSize, method ? method.length : 0); + maxEndpointSize = Math.max(maxEndpointSize, messageStore.getMessage().endpoint.length); + + const message = messageStore.getMessage(); + const seqNum = message.sequenceNumber; + const isActiveRequest = selectedReqSeqNum === seqNum; + const isFiltered = messageStore.isFiltered() || renderSet.length >= renderCount && !messageQueueStore.getFullPageSearch(); + if (isActiveRequest || !isFiltered) { + renderSet.push(messageStore); + } + if (isActiveRequest) activeReqSeqNumAdded = true; + } + + if (selectedReqSeqNum !== Number.MAX_SAFE_INTEGER && !activeReqSeqNumAdded) { + for (let i = 0; i < messageQueueStore.getMessages().length; ++i) { + const messageStore = messageQueueStore.getMessages()[i]; + if (selectedReqSeqNum === messageStore.getMessage().sequenceNumber) { + renderSet.push(messageStore); + break; + } + } + } let activeRequestIndex = Number.MAX_SAFE_INTEGER; - let matchCount = 0; let layout = snapshotStore.getLayout(snapshotStore.getSelectedSnapshotName()); if (layout === undefined) layout = new LayoutStore(); const vertical = layout.isVertical(); const requestContainerLayout = layout.requestContainer(selectedReqSeqNum === Number.MAX_SAFE_INTEGER); const responseContainerLayout = layout.responseContainer(selectedReqSeqNum === Number.MAX_SAFE_INTEGER); - - let renderedCount = 0; - let renderCount = calcRenderCount(scrollTop); - return (
- {messageQueueStore.getMessages().map((messageStore, index) => { + ref={requestContainerRef} onWheel={handleScroll}> + {renderSet.map((messageStore, index) => { const message = messageStore.getMessage(); const seqNum = message.sequenceNumber; const isActiveRequest = selectedReqSeqNum === seqNum; - const isFiltered = renderedCount >= renderCount && highlightSeqNum < seqNum && !messageQueueStore.getFullPageSearch() - || ((!snapshotStore.isUpdating() || snapshotStore.getUpdatingMessage().length === 0) && filterStore.isFilteredNoCache(messageStore)); - if (!isActiveRequest && isFiltered) { - return null; - } else { - ++renderedCount; - if (isActiveRequest) { - activeRequestIndex = index; - } - matchCount++; - return ( + + if (isActiveRequest) { + activeRequestIndex = index; + } + return ( + <> setClickPendingSeqNum(seqNum)} onResend={() => handleResend(message)} vertical={vertical} - isFiltered={isFiltered} - className={message.protocol === 'log:' && matchCount % 2 === 0 ? 'request__msg-even' : ''} - />); - } + isFiltered={messageStore.isFiltered()} + className={message.protocol === 'log:' && index % 2 === 0 ? 'request__msg-even' : ''} + /> + + ); })} - {matchCount === 0 && ( + {renderSet.length === 0 && (
No matching request or response found. Adjust your filter criteria.
@@ -268,33 +360,35 @@ const SnapshotTabContent = observer(({ } else { messageQueueStore.setScrollToSeqNum(seqNum); } + setHighlightSeqNum(seqNum); snapshotStore.setUpdating(false); }); } - function handleScroll() { + function handleScroll(e: any) { + const up = e.deltaY < 0; const parent = (requestContainerRef.current as Element); if (parent && parent.childNodes.length > 0) { - setScrollTop(parent.scrollTop); - // If the last message is visible, we need setFreeze(false) to cause - // all queued messages to be merged into the message queue, and become - // visible. - setTimeout(() => { - const parent = (requestContainerRef.current as Element); - if (parent && parent.childNodes.length > 0) { - const children = parent.childNodes; - let i = messageQueueStore.getMessages().length - 1; - i = Math.max(0, i - 1); - const element = (children[i] as Element); - if (element) { - const top = element.getBoundingClientRect().top; - if (top <= 800) { - messageQueueStore.setFreeze(false); - setTimeout(handleScroll, 1000); + if (selectedReqSeqNum === Number.MAX_SAFE_INTEGER) { + const bottom = endOfScroll() - parent.clientHeight; + //console.log(parent.scrollTop, scrollTop, bottom, renderSet[0].getIndex()); + if (messageQueueStore.getScrollAction() === undefined) { + const now = Date.now(); + const elapsed = now - lastScrollTime; + if (elapsed > 1000) { + lastScrollTime = now; + if (up && parent.scrollTop === 0 && scrollTop === 0 && renderSet[0].getIndex() > 0) { + messageQueueStore.setScrollAction('pageup'); + messageQueueStore.setScrollPending(true); + } else if (!up && parent.scrollTop + 1 >= bottom && parent.scrollTop === scrollTop && renderSet[renderSet.length - 1].getIndex() < messageQueueStore.getMessages().length - 1) { + messageQueueStore.setScrollAction('pagedown'); + messageQueueStore.setScrollPending(true); } } } - }); + } + + setScrollTop(parent.scrollTop); } } @@ -305,6 +399,21 @@ const SnapshotTabContent = observer(({ } } + function endOfScroll(): number { + let offset = 0; + const parent = (requestContainerRef.current as Element); + if (parent && parent.childNodes.length > 0) { + const children = parent.childNodes; + for (let i = 0; i < renderSet.length; ++i) { + const element = (children[i] as Element); + if (!element) break; + offset += element.clientHeight; + } + return offset; + } + return 0; + } + function doScrollTo(seqNum: number, delay: number = 0): boolean { if (seqNum !== Number.MAX_SAFE_INTEGER) { let offset = 0; @@ -314,11 +423,11 @@ const SnapshotTabContent = observer(({ const children = parent.childNodes; let elementIndex = 0; let entryHeight = 0; - for (let i = 0; i < messageQueueStore.getMessages().length; ++i) { - const msg = messageQueueStore.getMessages()[i].getMessage(); - if (filterStore.isFiltered(messageQueueStore.getMessages()[i])) continue; + for (const messageStore of renderSet) { + const message = messageStore.getMessage(); const element = (children[elementIndex] as Element); - if (msg.sequenceNumber === seqNum) { + if (!element) return false; + if (message.sequenceNumber === seqNum) { entryHeight = element.clientHeight; break; } @@ -332,24 +441,13 @@ const SnapshotTabContent = observer(({ offset + entryHeight > parent.scrollTop + parent.clientHeight) // below ) { parent.scrollTop = offset; - //setScrollTop(offset); + setScrollTop(offset); } } }, delay); } return true; } - - function calcRenderCount(scrollTop: number): number { - let renderCount = Number.MAX_SAFE_INTEGER; - const parent = (requestContainerRef.current as Element); - const height = parent ? parent.clientHeight : window.innerHeight; - const top = parent ? parent.scrollTop : scrollTop; - const scrollBottom = top + (height * 2); - renderCount = scrollBottom / minEntryHeight; - renderCount = Math.floor(renderCount); - return renderCount; - } }); export default SnapshotTabContent; \ No newline at end of file diff --git a/client/src/components/SnapshotTabs.tsx b/client/src/components/SnapshotTabs.tsx index d7603e0b..e0585a28 100644 --- a/client/src/components/SnapshotTabs.tsx +++ b/client/src/components/SnapshotTabs.tsx @@ -112,6 +112,10 @@ const SnapshotTabs = observer(({ messageQueueStore, snapshotStore }: Props) => { setScrollTop={ (scrollTop) => snapshotStore.getScrollTop()[i] = scrollTop } + scrollTopIndex={snapshotStore.getScrollTopIndex()[i]} + setScrollTopIndex={ + (scrollTop) => snapshotStore.getScrollTopIndex()[i] = scrollTop + } highlightSeqNum={snapshotStore.getHightlightSeqNum()[i]} setHighlightSeqNum={ (seqNum) => snapshotStore.getHightlightSeqNum()[i] = seqNum diff --git a/client/src/store/MessageQueueStore.ts b/client/src/store/MessageQueueStore.ts index df61895d..87f71255 100644 --- a/client/src/store/MessageQueueStore.ts +++ b/client/src/store/MessageQueueStore.ts @@ -10,9 +10,10 @@ const LOCAL_STORAGE_LIMIT = 'allproxy-limit'; export default class MessageQueueStore { private limit: number = this._getLimit(); private stopped: boolean = false; + private scrollPending: boolean = false; - private scrollToTop: boolean = false; - private scrollToBottom: boolean = false; + private scrollAction: 'top' | 'bottom' | 'pageup' | 'pagedown' | undefined = undefined; + private sortByReq: boolean = true; private freeze: boolean = false; private freezeQ: Message[] = []; @@ -152,20 +153,12 @@ export default class MessageQueueStore { this.scrollPending = scrollPending; } - public getScrollToTop(): boolean { - return this.scrollToTop; - } - - @action public setScrollToTop(top: boolean) { - this.scrollToTop = top; - } - - public getScrollToBottom(): boolean { - return this.scrollToBottom; + public getScrollAction(): 'top' | 'bottom' | 'pageup' | 'pagedown' | undefined { + return this.scrollAction; } - @action public setScrollToBottom(buttom: boolean) { - this.scrollToBottom = buttom; + @action public setScrollAction(action: 'top' | 'bottom' | 'pageup' | 'pagedown' | undefined) { + this.scrollAction = action; } public getSortByReq(): boolean { diff --git a/client/src/store/MessageStore.ts b/client/src/store/MessageStore.ts index 94586133..733a88f5 100644 --- a/client/src/store/MessageStore.ts +++ b/client/src/store/MessageStore.ts @@ -7,6 +7,7 @@ import { LogEntry, jsonLogStore, JsonField, formatJSONRequestLabels as findMatch import { snapshotStore } from "./SnapshotStore"; class MessageStoreBase { + private index: number = 0; private message: Message = new Message(); private url = ''; private _isError = false; @@ -40,6 +41,14 @@ class MessageStoreBase { makeAutoObservable(this); } + public setIndex(index: number) { + this.index = index; + } + + public getIndex() { + return this.index; + } + public isFiltered() { return this.filtered; } diff --git a/client/src/store/SnapshotStore.ts b/client/src/store/SnapshotStore.ts index e4900c66..faa5da88 100644 --- a/client/src/store/SnapshotStore.ts +++ b/client/src/store/SnapshotStore.ts @@ -14,6 +14,7 @@ class Snapshots { private names: string[] = []; private selectedReqSeqNumbers: number[] = []; private scrollTop: number[] = []; + private scrollTopIndex: number[] = []; private highlightSeqNum: number[] = []; private fileNameMap: Map = new Map(); private jsonPrimaryFieldsMap: Map = new Map(); @@ -37,11 +38,13 @@ class Snapshots { jsonFields: { name: string, count: number, selected: boolean }[] = [], layout: LayoutStore = new LayoutStore(), highlightSeqNum = Number.MAX_SAFE_INTEGER, + scrollTopIndex = 0 ) { this.snapshots.set(key, snapshot); this.names.push(key); this.selectedReqSeqNumbers.push(selectedReqSeqNumber); this.scrollTop.push(scrollTop); + this.scrollTopIndex.push(scrollTopIndex); this.highlightSeqNum.push(highlightSeqNum); if (fileName) { this.fileNameMap.set(key, fileName); @@ -56,6 +59,7 @@ class Snapshots { this.names.splice(index, 1); this.selectedReqSeqNumbers.splice(index, 1); this.scrollTop.splice(index, 1); + this.scrollTopIndex.splice(index, 1); this.highlightSeqNum.splice(index, 1); this.fileNameMap.delete(key); this.jsonPrimaryFieldsMap.delete(key); @@ -78,6 +82,10 @@ class Snapshots { return this.scrollTop; } + public getScrollTopIndex(): number[] { + return this.scrollTopIndex; + } + public getHighlightSeqNum(): number[] { return this.highlightSeqNum; } @@ -166,6 +174,10 @@ export default class SnapshotStore { return this.snapshots.getScrollTop(); } + public getScrollTopIndex(): number[] { + return this.snapshots.getScrollTopIndex(); + } + public getHightlightSeqNum(): number[] { return this.snapshots.getHighlightSeqNum(); } From d6ff980b1a37db072f5cdccc4942815bfd1179a2 Mon Sep 17 00:00:00 2001 From: Dave Christenson Date: Sat, 11 Nov 2023 20:45:14 -0600 Subject: [PATCH 2/3] electron version update --- package-electron.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-electron.json b/package-electron.json index 7a1892a5..d3d5b0eb 100644 --- a/package-electron.json +++ b/package-electron.json @@ -1,6 +1,6 @@ { "name": "allproxy", - "version": "3.34.11", + "version": "3.35.0", "description": "AllProxy: MITM HTTP Debugging Tool.", "keywords": [ "proxy", From 895873af04d774154dd0e21da5c20fed36bae98a Mon Sep 17 00:00:00 2001 From: Dave Christenson Date: Sat, 11 Nov 2023 20:46:42 -0600 Subject: [PATCH 3/3] 3.35.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9409075c..839a4901 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "allproxy", - "version": "3.34.11", + "version": "3.35.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "allproxy", - "version": "3.34.11", + "version": "3.35.0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 7b346894..7c02bf43 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "allproxy", - "version": "3.34.11", + "version": "3.35.0", "description": "AllProxy: MITM HTTP Debugging Tool.", "keywords": [ "proxy",