fix(desktop): reduce grid scroll jank
This commit is contained in:
parent
c87d42fb5e
commit
87efe469a4
|
|
@ -110,6 +110,7 @@ const view = shallowRef<EditorViewType | null>(null);
|
|||
let viewportEmitFrame: number | null = null;
|
||||
let viewportRestoreFrame: number | null = null;
|
||||
let latestViewport: { scrollTop: number; scrollLeft: number } | undefined = props.initialViewport;
|
||||
let lastEmittedViewport: { scrollTop: number; scrollLeft: number } | undefined = props.initialViewport;
|
||||
let latestSelection: { anchor: number; head: number } | undefined = props.initialSelection;
|
||||
const connectionStore = useConnectionStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
|
@ -3226,6 +3227,10 @@ function readEditorViewport(currentView: EditorViewType) {
|
|||
};
|
||||
}
|
||||
|
||||
function sameEditorViewport(a: { scrollTop: number; scrollLeft: number } | undefined, b: { scrollTop: number; scrollLeft: number }) {
|
||||
return a?.scrollTop === b.scrollTop && a.scrollLeft === b.scrollLeft;
|
||||
}
|
||||
|
||||
function normalizedEditorSelection(selection: { anchor: number; head: number } | undefined, docLength: number) {
|
||||
if (!selection) return undefined;
|
||||
return {
|
||||
|
|
@ -3270,6 +3275,8 @@ function restoreEditorFocus() {
|
|||
}
|
||||
|
||||
function emitEditorViewport(viewport: { scrollTop: number; scrollLeft: number }) {
|
||||
if (sameEditorViewport(lastEmittedViewport, viewport)) return;
|
||||
lastEmittedViewport = { ...viewport };
|
||||
emit("viewportChange", viewport);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ import { getDataGridConditionSuggestionPosition } from "@/lib/dataGrid/dataGridC
|
|||
import { caretPositionInsideInsertedSqlSingleQuotes, insertedSqlSingleQuoteAtCaret } from "@/lib/sql/sqlQuoteCaret";
|
||||
import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||
import { isMacOS } from "@/lib/backend/platform";
|
||||
import { appendDebugLog, isDebugLoggingEnabled } from "@/lib/backend/debugLog";
|
||||
import { formatShortcut } from "@/lib/editor/shortcutRegistry";
|
||||
import { SearchableSelect } from "@/components/ui/searchable-select";
|
||||
|
||||
|
|
@ -252,6 +253,10 @@ const saveShortcutLabel = computed(() => formatShortcut(settingsStore.editorSett
|
|||
const DATA_GRID_COMPACT_TOPBAR_WIDTH = 900;
|
||||
const AUTO_REFRESH_INTERVAL_OPTIONS = [5, 10, 30, 60, 300];
|
||||
|
||||
function logDataGridTiming(message: string, payload?: Record<string, unknown>) {
|
||||
appendDebugLog("info", message, payload);
|
||||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
reload: [sql?: string, searchText?: string, whereInput?: string, orderBy?: string, limit?: number, offset?: number];
|
||||
paginate: [offset: number, limit: number, whereInput?: string, orderBy?: string];
|
||||
|
|
@ -265,14 +270,16 @@ const autoRefreshEnabled = ref(false);
|
|||
let autoRefreshTimer: ReturnType<typeof setInterval> | undefined;
|
||||
const autoRefreshLabel = computed(() => (autoRefreshEnabled.value ? t("tabs.autoRefreshEvery", { seconds: autoRefreshIntervalSeconds.value }) : t("tabs.autoRefresh")));
|
||||
|
||||
console.info("[DBX][DataGrid:setup]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
rowCount: props.result.rows.length,
|
||||
columnCount: props.result.columns.length,
|
||||
backendMs: props.result.execution_time_ms,
|
||||
loading: props.loading,
|
||||
});
|
||||
if (isDebugLoggingEnabled()) {
|
||||
logDataGridTiming("[DBX][DataGrid:setup]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
rowCount: props.result.rows.length,
|
||||
columnCount: props.result.columns.length,
|
||||
backendMs: props.result.execution_time_ms,
|
||||
loading: props.loading,
|
||||
});
|
||||
}
|
||||
|
||||
const transposeRowIndex = ref<number | null>(null);
|
||||
const showTranspose = ref(false);
|
||||
|
|
@ -282,8 +289,9 @@ const preserveTransposeOnNextResult = ref(false);
|
|||
watch(
|
||||
() => props.result,
|
||||
(result) => {
|
||||
if (!isDebugLoggingEnabled()) return;
|
||||
const startedAt = performance.now();
|
||||
console.info("[DBX][DataGrid:result:prop]", {
|
||||
logDataGridTiming("[DBX][DataGrid:result:prop]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
rowCount: result.rows.length,
|
||||
|
|
@ -294,14 +302,14 @@ watch(
|
|||
});
|
||||
|
||||
nextTick(() => {
|
||||
console.info("[DBX][DataGrid:result:nextTick]", {
|
||||
logDataGridTiming("[DBX][DataGrid:result:nextTick]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
elapsed: `${Math.round(performance.now() - startedAt)}ms`,
|
||||
loading: props.loading,
|
||||
});
|
||||
requestAnimationFrame(() => {
|
||||
console.info("[DBX][DataGrid:result:first-frame]", {
|
||||
logDataGridTiming("[DBX][DataGrid:result:first-frame]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
elapsed: `${Math.round(performance.now() - startedAt)}ms`,
|
||||
|
|
@ -2471,6 +2479,8 @@ const gridVerticalScrollbarDragging = ref(false);
|
|||
let gridHorizontalScrollbarFrame = 0;
|
||||
let gridHorizontalScrollbarDragFrame = 0;
|
||||
let gridHorizontalScrollbarPendingClientX = 0;
|
||||
let gridVerticalScrollbarDragFrame = 0;
|
||||
let gridVerticalScrollbarPendingClientY = 0;
|
||||
let gridHorizontalScrollbarResizeObserver: ResizeObserver | null = null;
|
||||
let dataGridTopbarResizeObserver: ResizeObserver | null = null;
|
||||
let cellEditResizeObserver: ResizeObserver | null = null;
|
||||
|
|
@ -2482,8 +2492,10 @@ let gridHorizontalScrollbarDragState: {
|
|||
maxScrollLeft: number;
|
||||
} | null = null;
|
||||
let gridVerticalScrollbarDragState: {
|
||||
scroller: HTMLElement;
|
||||
trackRect: DOMRect;
|
||||
thumbOffsetPx: number;
|
||||
maxScrollTop: number;
|
||||
} | null = null;
|
||||
const hiddenColumnIndexes = ref<Set<number>>(new Set());
|
||||
const nullColumnsHidden = ref(false);
|
||||
|
|
@ -2856,6 +2868,7 @@ function stopGridHorizontalScrollbarDrag() {
|
|||
|
||||
function stopGridVerticalScrollbarDrag() {
|
||||
if (!gridVerticalScrollbarDragState) return;
|
||||
flushGridVerticalScrollbarDrag();
|
||||
gridVerticalScrollbarDragState = null;
|
||||
gridVerticalScrollbarDragging.value = false;
|
||||
window.removeEventListener("pointermove", onGridVerticalScrollbarPointerMove, true);
|
||||
|
|
@ -2892,26 +2905,42 @@ function startGridHorizontalScrollbarDrag(event: PointerEvent) {
|
|||
scheduleGridHorizontalScrollbarDrag(event.clientX);
|
||||
}
|
||||
|
||||
function applyGridVerticalScrollbarDrag(clientY: number) {
|
||||
const scroller = gridScrollerElement();
|
||||
function applyPendingGridVerticalScrollbarDrag() {
|
||||
gridVerticalScrollbarDragFrame = 0;
|
||||
const dragState = gridVerticalScrollbarDragState;
|
||||
if (!scroller || !dragState) return;
|
||||
|
||||
const maxScrollTop = Math.max(0, scroller.scrollHeight - scroller.clientHeight);
|
||||
if (maxScrollTop <= 1) return;
|
||||
if (!dragState) return;
|
||||
|
||||
const thumbHeightPx = dragState.trackRect.height * (gridVerticalScrollbarThumbHeightPercent.value / 100);
|
||||
const maxThumbTopPx = Math.max(1, dragState.trackRect.height - thumbHeightPx);
|
||||
const thumbTopPx = Math.min(maxThumbTopPx, Math.max(0, clientY - dragState.trackRect.top - dragState.thumbOffsetPx));
|
||||
scroller.scrollTop = (thumbTopPx / maxThumbTopPx) * maxScrollTop;
|
||||
const thumbTopPx = Math.min(maxThumbTopPx, Math.max(0, gridVerticalScrollbarPendingClientY - dragState.trackRect.top - dragState.thumbOffsetPx));
|
||||
const scroller = dragState.scroller;
|
||||
const nextScrollTop = (thumbTopPx / maxThumbTopPx) * dragState.maxScrollTop;
|
||||
if (Math.abs(scroller.scrollTop - nextScrollTop) < 0.5) return;
|
||||
scroller.scrollTop = nextScrollTop;
|
||||
updateGridVerticalScrollbar(scroller);
|
||||
if (useCanvasGridRows.value) syncCanvasViewport();
|
||||
if (useCanvasGridRows.value) {
|
||||
canvasScrollTop.value = scroller.scrollTop;
|
||||
drawCanvasGridNow();
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleGridVerticalScrollbarDrag(clientY: number) {
|
||||
gridVerticalScrollbarPendingClientY = clientY;
|
||||
if (gridVerticalScrollbarDragFrame) return;
|
||||
// Match horizontal dragging: coalesce pointermove bursts to one canvas/layout update per frame.
|
||||
gridVerticalScrollbarDragFrame = requestAnimationFrame(applyPendingGridVerticalScrollbarDrag);
|
||||
}
|
||||
|
||||
function flushGridVerticalScrollbarDrag() {
|
||||
if (!gridVerticalScrollbarDragFrame) return;
|
||||
cancelAnimationFrame(gridVerticalScrollbarDragFrame);
|
||||
applyPendingGridVerticalScrollbarDrag();
|
||||
}
|
||||
|
||||
function onGridVerticalScrollbarPointerMove(event: PointerEvent) {
|
||||
if (!gridVerticalScrollbarDragState) return;
|
||||
event.preventDefault();
|
||||
applyGridVerticalScrollbarDrag(event.clientY);
|
||||
scheduleGridVerticalScrollbarDrag(event.clientY);
|
||||
}
|
||||
|
||||
function startGridVerticalScrollbarDrag(event: PointerEvent) {
|
||||
|
|
@ -2919,6 +2948,8 @@ function startGridVerticalScrollbarDrag(event: PointerEvent) {
|
|||
const track = gridVerticalScrollbarTrackRef.value;
|
||||
if (!scroller || !track || !hasGridVerticalOverflow.value) return;
|
||||
|
||||
const maxScrollTop = Math.max(0, scroller.scrollHeight - scroller.clientHeight);
|
||||
if (maxScrollTop <= 1) return;
|
||||
const trackRect = track.getBoundingClientRect();
|
||||
const thumbTopPx = trackRect.height * (gridVerticalScrollbarThumbTopPercent.value / 100);
|
||||
const thumbHeightPx = trackRect.height * (gridVerticalScrollbarThumbHeightPercent.value / 100);
|
||||
|
|
@ -2926,8 +2957,10 @@ function startGridVerticalScrollbarDrag(event: PointerEvent) {
|
|||
const pointerInsideThumb = pointerY >= thumbTopPx && pointerY <= thumbTopPx + thumbHeightPx;
|
||||
|
||||
gridVerticalScrollbarDragState = {
|
||||
scroller,
|
||||
trackRect,
|
||||
thumbOffsetPx: pointerInsideThumb ? pointerY - thumbTopPx : thumbHeightPx / 2,
|
||||
maxScrollTop,
|
||||
};
|
||||
gridVerticalScrollbarDragging.value = true;
|
||||
document.body.style.userSelect = "none";
|
||||
|
|
@ -2935,7 +2968,7 @@ function startGridVerticalScrollbarDrag(event: PointerEvent) {
|
|||
window.addEventListener("pointerup", stopGridVerticalScrollbarDrag, true);
|
||||
window.addEventListener("pointercancel", stopGridVerticalScrollbarDrag, true);
|
||||
event.preventDefault();
|
||||
applyGridVerticalScrollbarDrag(event.clientY);
|
||||
scheduleGridVerticalScrollbarDrag(event.clientY);
|
||||
}
|
||||
|
||||
const gridHorizontalScrollbarThumbStyle = computed<CSSProperties>(() => ({
|
||||
|
|
@ -4258,23 +4291,27 @@ const displayItems = computed<RowItem[]>(() => displayRowRefs.value.map(rowItemF
|
|||
watch(
|
||||
() => displayRowCount.value,
|
||||
(length) => {
|
||||
const startedAt = performance.now();
|
||||
console.info("[DBX][DataGrid:display-items:ready]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
displayItemCount: length,
|
||||
sourceRowCount: props.result.rows.length,
|
||||
elapsedSinceSetup: dataGridElapsed(),
|
||||
});
|
||||
const shouldLogTiming = isDebugLoggingEnabled();
|
||||
const startedAt = shouldLogTiming ? performance.now() : 0;
|
||||
if (shouldLogTiming) {
|
||||
logDataGridTiming("[DBX][DataGrid:display-items:ready]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
displayItemCount: length,
|
||||
sourceRowCount: props.result.rows.length,
|
||||
elapsedSinceSetup: dataGridElapsed(),
|
||||
});
|
||||
}
|
||||
nextTick(() => {
|
||||
const scrollerEl = gridRef.value?.querySelector<HTMLElement>(".data-grid-scroller");
|
||||
if (scrollerEl) {
|
||||
updateGridScrollbarGutter(scrollerEl);
|
||||
updateGridHorizontalViewport(scrollerEl);
|
||||
}
|
||||
if (!shouldLogTiming) return;
|
||||
requestAnimationFrame(() => {
|
||||
const renderedRows = gridRef.value?.querySelectorAll(".vue-recycle-scroller__item-view").length;
|
||||
console.info("[DBX][DataGrid:display-items:first-frame]", {
|
||||
logDataGridTiming("[DBX][DataGrid:display-items:first-frame]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
displayItemCount: length,
|
||||
|
|
@ -5961,10 +5998,12 @@ watch(
|
|||
detailCell,
|
||||
showCellDetail,
|
||||
editingCell,
|
||||
// Pending edit structures can contain large nested cell maps; the editor
|
||||
// version ref gives the canvas a cheap invalidation signal without a deep watch.
|
||||
pendingChangesVersion,
|
||||
],
|
||||
scheduleCanvasDraw,
|
||||
);
|
||||
watch([dirtyRows, newRows, deletedRows], scheduleCanvasDraw, { deep: true });
|
||||
|
||||
function pauseCanvasGridWork() {
|
||||
dataGridIsActive = false;
|
||||
|
|
@ -6022,6 +6061,9 @@ onUnmounted(() => {
|
|||
if (gridHorizontalScrollbarFrame && typeof cancelAnimationFrame === "function") {
|
||||
cancelAnimationFrame(gridHorizontalScrollbarFrame);
|
||||
}
|
||||
if (gridVerticalScrollbarDragFrame && typeof cancelAnimationFrame === "function") {
|
||||
cancelAnimationFrame(gridVerticalScrollbarDragFrame);
|
||||
}
|
||||
if (typeof window === "undefined") return;
|
||||
window.removeEventListener("resize", scheduleCanvasPixelRatioRefresh);
|
||||
window.removeEventListener("resize", resizeFocusedConditionInputs);
|
||||
|
|
@ -7974,23 +8016,27 @@ watch(
|
|||
() => props.loading,
|
||||
(isLoading) => {
|
||||
stopLoadingElapsedTimer();
|
||||
console.info(isLoading ? "[DBX][DataGrid:loading:start]" : "[DBX][DataGrid:loading:stop]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
elapsedSinceSetup: dataGridElapsed(),
|
||||
});
|
||||
if (isDebugLoggingEnabled()) {
|
||||
logDataGridTiming(isLoading ? "[DBX][DataGrid:loading:start]" : "[DBX][DataGrid:loading:stop]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
elapsedSinceSetup: dataGridElapsed(),
|
||||
});
|
||||
}
|
||||
if (isLoading) {
|
||||
startLoadingElapsedTimer();
|
||||
} else {
|
||||
nextTick(() => {
|
||||
requestAnimationFrame(() => {
|
||||
console.info("[DBX][DataGrid:loading:stop:first-frame]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
elapsedSinceSetup: dataGridElapsed(),
|
||||
if (isDebugLoggingEnabled()) {
|
||||
nextTick(() => {
|
||||
requestAnimationFrame(() => {
|
||||
logDataGridTiming("[DBX][DataGrid:loading:stop:first-frame]", {
|
||||
traceId: dataGridTraceId,
|
||||
cacheKey: props.cacheKey,
|
||||
elapsedSinceSetup: dataGridElapsed(),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref, defineAsyncComponent, watch, nextTick, onMounted, onUnmounted } from "vue";
|
||||
import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStorage";
|
||||
import { appendDebugLog, isDebugLoggingEnabled } from "@/lib/backend/debugLog";
|
||||
import type { CSSProperties } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Check, Columns3, EyeOff, Loader2, Search, GitBranch, BarChart3, TableProperties, ChevronDown, ChevronUp, Inbox, RefreshCcw, Timer, Wrench, Toolbox, ListChecks, Database, Download, Upload, X, Pin, Rows3, SquareDashed, Minus, Plus } from "@lucide/vue";
|
||||
|
|
@ -20,10 +21,11 @@ let dataGridComponentPromise: Promise<typeof import("@/components/grid/DataGrid.
|
|||
function loadDataGridComponent() {
|
||||
if (!dataGridComponentPromise) {
|
||||
dataGridComponentPromise = (async () => {
|
||||
const startedAt = performance.now();
|
||||
console.info("[DBX][DataGrid:load:start]");
|
||||
const shouldLogTiming = isDebugLoggingEnabled();
|
||||
const startedAt = shouldLogTiming ? performance.now() : 0;
|
||||
if (shouldLogTiming) appendDebugLog("info", "[DBX][DataGrid:load:start]");
|
||||
const component = await import("@/components/grid/DataGrid.vue");
|
||||
console.info("[DBX][DataGrid:load:done]", { elapsed: `${Math.round(performance.now() - startedAt)}ms` });
|
||||
if (shouldLogTiming) appendDebugLog("info", "[DBX][DataGrid:load:done]", { elapsed: `${Math.round(performance.now() - startedAt)}ms` });
|
||||
return component;
|
||||
})();
|
||||
}
|
||||
|
|
@ -477,8 +479,9 @@ watch(
|
|||
() => props.activeTab.result,
|
||||
(result) => {
|
||||
if (!result) return;
|
||||
if (!isDebugLoggingEnabled()) return;
|
||||
const startedAt = performance.now();
|
||||
console.info("[DBX][ContentArea:result:observed]", {
|
||||
appendDebugLog("info", "[DBX][ContentArea:result:observed]", {
|
||||
tabId: props.activeTab.id,
|
||||
rowCount: result.rows.length,
|
||||
columnCount: result.columns.length,
|
||||
|
|
@ -486,13 +489,13 @@ watch(
|
|||
isExecuting: props.activeTab.isExecuting,
|
||||
});
|
||||
nextTick(() => {
|
||||
console.info("[DBX][ContentArea:result:nextTick]", {
|
||||
appendDebugLog("info", "[DBX][ContentArea:result:nextTick]", {
|
||||
tabId: props.activeTab.id,
|
||||
elapsed: `${Math.round(performance.now() - startedAt)}ms`,
|
||||
isExecuting: props.activeTab.isExecuting,
|
||||
});
|
||||
requestAnimationFrame(() => {
|
||||
console.info("[DBX][ContentArea:result:first-frame]", {
|
||||
appendDebugLog("info", "[DBX][ContentArea:result:first-frame]", {
|
||||
tabId: props.activeTab.id,
|
||||
elapsed: `${Math.round(performance.now() - startedAt)}ms`,
|
||||
isExecuting: props.activeTab.isExecuting,
|
||||
|
|
|
|||
|
|
@ -322,6 +322,9 @@ export const useQueryStore = defineStore("query", () => {
|
|||
const closeConfirmContext = ref<CloseConfirmContext>("tab");
|
||||
const tableStructureRefreshVersions = ref<Record<string, number>>({});
|
||||
const savedSqlEditorPositionTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
||||
let resultCacheTrimScheduled = false;
|
||||
let resultCacheTrimRunning = false;
|
||||
let resultCacheTrimRequested = false;
|
||||
|
||||
function tableStructureKey(connectionId: string, database: string, schema: string | undefined, tableName: string): string {
|
||||
return [connectionId, database, schema || "", tableName].map((part) => part.toLowerCase()).join("\u0000");
|
||||
|
|
@ -1561,6 +1564,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
function updateEditorViewport(id: string, viewport: { scrollTop: number; scrollLeft: number }) {
|
||||
const tab = tabs.value.find((t) => t.id === id);
|
||||
if (!tab) return;
|
||||
if (tab.editorViewport?.scrollTop === viewport.scrollTop && tab.editorViewport?.scrollLeft === viewport.scrollLeft) return;
|
||||
tab.editorViewport = viewport;
|
||||
queueSavedSqlEditorPositionPersist(tab);
|
||||
}
|
||||
|
|
@ -2691,7 +2695,7 @@ export const useQueryStore = defineStore("query", () => {
|
|||
});
|
||||
}
|
||||
}
|
||||
await trimResultCache();
|
||||
scheduleResultCacheTrim();
|
||||
}
|
||||
|
||||
async function explainTabSql(id: string, sql: string, databaseType?: DatabaseType, explainMode?: string) {
|
||||
|
|
@ -2887,6 +2891,39 @@ export const useQueryStore = defineStore("query", () => {
|
|||
}
|
||||
}
|
||||
|
||||
function scheduleResultCacheTrim() {
|
||||
resultCacheTrimRequested = true;
|
||||
if (resultCacheTrimScheduled || resultCacheTrimRunning) return;
|
||||
resultCacheTrimScheduled = true;
|
||||
|
||||
const run = () => {
|
||||
resultCacheTrimScheduled = false;
|
||||
void runRequestedResultCacheTrim();
|
||||
};
|
||||
|
||||
// Eviction serializes large result payloads; schedule it after the result
|
||||
// assignment so the grid can paint before cache maintenance starts.
|
||||
if (typeof window !== "undefined" && "requestIdleCallback" in window) {
|
||||
window.requestIdleCallback(run, { timeout: 1500 });
|
||||
} else {
|
||||
setTimeout(run, 0);
|
||||
}
|
||||
}
|
||||
|
||||
async function runRequestedResultCacheTrim() {
|
||||
if (resultCacheTrimRunning) return;
|
||||
resultCacheTrimRunning = true;
|
||||
try {
|
||||
while (resultCacheTrimRequested) {
|
||||
resultCacheTrimRequested = false;
|
||||
await trimResultCache();
|
||||
}
|
||||
} finally {
|
||||
resultCacheTrimRunning = false;
|
||||
if (resultCacheTrimRequested) scheduleResultCacheTrim();
|
||||
}
|
||||
}
|
||||
|
||||
function rememberActiveTab(id: string | null) {
|
||||
if (!id || !tabs.value.some((tab) => tab.id === id)) return;
|
||||
activeTabHistory.value = [...activeTabHistory.value.filter((tabId) => tabId !== id), id];
|
||||
|
|
|
|||
|
|
@ -1686,6 +1686,7 @@ test("evicting cached tab results releases multi-result payloads and sessions",
|
|||
await store.executeTabSql(tabId, `select ${i + 1}; select ${i + 1} as detail`);
|
||||
}
|
||||
|
||||
await waitFor(() => store.tabs.find((tab) => tab.id === tabIds[0])?.resultEvicted === true);
|
||||
const evicted = store.tabs.find((tab) => tab.id === tabIds[0]);
|
||||
assert.equal(executeCount, 7);
|
||||
assert.equal(evicted?.result, undefined);
|
||||
|
|
@ -1761,6 +1762,7 @@ test("result cache eviction keeps recently accessed inactive tabs", async () =>
|
|||
tabIds.push(tabId);
|
||||
await store.executeTabSql(tabId, "select 7");
|
||||
|
||||
await waitFor(() => store.tabs.find((tab) => tab.id === tabIds[1])?.resultEvicted === true);
|
||||
const recentlyViewed = store.tabs.find((tab) => tab.id === tabIds[0]);
|
||||
const leastRecentlyUsed = store.tabs.find((tab) => tab.id === tabIds[1]);
|
||||
assert.ok(recentlyViewed?.result);
|
||||
|
|
|
|||
Loading…
Reference in New Issue