mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-22 08:53:46 +08:00
⚡️ perf(window): 降低空闲状态原生轮询频率
This commit is contained in:
@@ -459,11 +459,36 @@ describe('settings center tool entries', () => {
|
||||
expect(appSource).toContain('const unsubscribeHydration = useStore.persist.onFinishHydration(() => {');
|
||||
expect(appSource).toContain('scheduleWindowBoundsRepair();');
|
||||
expect(appSource).toContain('scheduleWindowStateSave(260);');
|
||||
expect(appSource).toContain("window.addEventListener('resize', handleWindowRuntimeChange);");
|
||||
expect(appSource).toContain("window.addEventListener('focus', handleWindowRuntimeChange);");
|
||||
expect(appSource).toContain("window.addEventListener('pageshow', handleWindowRuntimeChange);");
|
||||
expect(appSource).toContain("window.addEventListener('pagehide', handleWindowLifecycleFlush, { capture: true });");
|
||||
expect(appSource).toContain("window.addEventListener('beforeunload', handleWindowLifecycleFlush, { capture: true });");
|
||||
expect(appSource).toContain('const cleanupWindowActivityScheduler = installNativeWindowActivityScheduler({');
|
||||
expect(appSource).toContain('fallbackIntervalMs: WINDOW_STATE_FALLBACK_INTERVAL_MS,');
|
||||
expect(appSource).toContain('resize: handleWindowRuntimeChange,');
|
||||
expect(appSource).toContain('focus: handleWindowRuntimeChange,');
|
||||
expect(appSource).toContain('pageshow: handleWindowRuntimeChange,');
|
||||
expect(appSource).toContain('pagehide: handleWindowLifecycleFlush,');
|
||||
expect(appSource).toContain('beforeunload: handleWindowLifecycleFlush,');
|
||||
expect(appSource).toContain('cleanupWindowActivityScheduler();');
|
||||
});
|
||||
|
||||
it('keeps the resize minimise probe independent from DPR debounce and clears it on unmount', () => {
|
||||
const scaleEffectStart = appSource.indexOf('let minimisedCheckTimer: number | null = null;');
|
||||
const dprScheduleStart = appSource.indexOf('const scheduleDevicePixelRatioCheck = (trigger: WindowsScaleCheckTrigger) => {', scaleEffectStart);
|
||||
const activationScheduleStart = appSource.indexOf('const scheduleActivationFix = () => {', dprScheduleStart);
|
||||
const resizeHandlerStart = appSource.indexOf('const handleWindowResize = () => {', activationScheduleStart);
|
||||
const startupFixStart = appSource.indexOf('// Windows 冷启动:', resizeHandlerStart);
|
||||
const schedulerStart = appSource.indexOf('fallbackIntervalMs: WINDOWS_SCALE_FALLBACK_INTERVAL_MS,', startupFixStart);
|
||||
const cleanupStart = appSource.indexOf('return () => {', schedulerStart);
|
||||
const cleanupEnd = appSource.indexOf('cleanupWindowActivityScheduler();', cleanupStart);
|
||||
|
||||
expect([scaleEffectStart, dprScheduleStart, activationScheduleStart, resizeHandlerStart, startupFixStart, schedulerStart, cleanupStart, cleanupEnd]
|
||||
.every((index) => index >= 0)).toBe(true);
|
||||
expect(appSource.slice(dprScheduleStart, activationScheduleStart)).not.toContain('minimisedCheckTimer');
|
||||
const resizeHandlerSource = appSource.slice(resizeHandlerStart, startupFixStart);
|
||||
const minimiseProbeIndex = resizeHandlerSource.indexOf('rememberMinimisedStateSoon();');
|
||||
const dprCheckIndex = resizeHandlerSource.indexOf("scheduleDevicePixelRatioCheck('resize');");
|
||||
expect(minimiseProbeIndex).toBeGreaterThan(-1);
|
||||
expect(dprCheckIndex).toBeGreaterThan(minimiseProbeIndex);
|
||||
expect(appSource.slice(cleanupStart, cleanupEnd)).toContain('window.clearTimeout(minimisedCheckTimer);');
|
||||
expect(appSource.slice(cleanupStart, cleanupEnd)).toContain('minimisedCheckTimer = null;');
|
||||
});
|
||||
|
||||
it('clamps normal runtime window bounds back into the visible screen after display changes', () => {
|
||||
|
||||
@@ -171,7 +171,18 @@ import {
|
||||
resolveDockedActiveTabId,
|
||||
type CloseShortcutScope,
|
||||
} from './utils/closeTabShortcut';
|
||||
import { resolveTitleBarToggleIconKey, resolveWindowsScaleCheckDelayMs, shouldApplyWindowsScaleFix, shouldResetWebViewZoomForScaleFix, shouldToggleMaximisedWindowForScaleFix, type WindowScaleFixReason, type WindowsScaleCheckTrigger } from './utils/windowStateUi';
|
||||
import {
|
||||
installNativeWindowActivityScheduler,
|
||||
resolveTitleBarToggleIconKey,
|
||||
resolveWindowsScaleCheckDelayMs,
|
||||
shouldApplyWindowsScaleFix,
|
||||
shouldResetWebViewZoomForScaleFix,
|
||||
shouldToggleMaximisedWindowForScaleFix,
|
||||
WINDOW_STATE_FALLBACK_INTERVAL_MS,
|
||||
WINDOWS_SCALE_FALLBACK_INTERVAL_MS,
|
||||
type WindowScaleFixReason,
|
||||
type WindowsScaleCheckTrigger,
|
||||
} from './utils/windowStateUi';
|
||||
import { resolveVisibleStartupWindowBounds } from './utils/windowRestoreBounds';
|
||||
import { resolveWailsWindowVisibleViewport } from './utils/wailsWindowViewport';
|
||||
import {
|
||||
@@ -1472,7 +1483,6 @@ function App() {
|
||||
|
||||
// 定时保存窗口状态、尺寸与位置
|
||||
useEffect(() => {
|
||||
const SAVE_INTERVAL_MS = 2000;
|
||||
let cancelled = false;
|
||||
let hydrated = useStore.persist.hasHydrated();
|
||||
let eventSaveTimer: number | null = null;
|
||||
@@ -1645,15 +1655,22 @@ function App() {
|
||||
scheduleWindowStateSave(320);
|
||||
});
|
||||
|
||||
const timer = window.setInterval(() => {
|
||||
void saveWindowState();
|
||||
}, SAVE_INTERVAL_MS);
|
||||
window.addEventListener('resize', handleWindowRuntimeChange);
|
||||
window.addEventListener('focus', handleWindowRuntimeChange);
|
||||
window.addEventListener('pageshow', handleWindowRuntimeChange);
|
||||
window.addEventListener('pagehide', handleWindowLifecycleFlush, { capture: true });
|
||||
window.addEventListener('beforeunload', handleWindowLifecycleFlush, { capture: true });
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
const cleanupWindowActivityScheduler = installNativeWindowActivityScheduler({
|
||||
windowTarget: window,
|
||||
documentTarget: document,
|
||||
fallbackIntervalMs: WINDOW_STATE_FALLBACK_INTERVAL_MS,
|
||||
onFallback: () => {
|
||||
void saveWindowState();
|
||||
},
|
||||
handlers: {
|
||||
resize: handleWindowRuntimeChange,
|
||||
focus: handleWindowRuntimeChange,
|
||||
pageshow: handleWindowRuntimeChange,
|
||||
pagehide: handleWindowLifecycleFlush,
|
||||
beforeunload: handleWindowLifecycleFlush,
|
||||
visibilitychange: handleVisibilityChange,
|
||||
},
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
if (eventSaveTimer !== null) {
|
||||
@@ -1662,13 +1679,7 @@ function App() {
|
||||
if (boundsRepairTimer !== null) {
|
||||
window.clearTimeout(boundsRepairTimer);
|
||||
}
|
||||
window.clearInterval(timer);
|
||||
window.removeEventListener('resize', handleWindowRuntimeChange);
|
||||
window.removeEventListener('focus', handleWindowRuntimeChange);
|
||||
window.removeEventListener('pageshow', handleWindowRuntimeChange);
|
||||
window.removeEventListener('pagehide', handleWindowLifecycleFlush, { capture: true });
|
||||
window.removeEventListener('beforeunload', handleWindowLifecycleFlush, { capture: true });
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
cleanupWindowActivityScheduler();
|
||||
unsubscribeHydration();
|
||||
};
|
||||
}, []);
|
||||
@@ -1684,6 +1695,7 @@ function App() {
|
||||
let lastFixAt = 0;
|
||||
let activationTimer: number | null = null;
|
||||
let resizeTimer: number | null = null;
|
||||
let minimisedCheckTimer: number | null = null;
|
||||
let minimisedSeen = false;
|
||||
let hiddenSeen = document.visibilityState === 'hidden';
|
||||
|
||||
@@ -1802,7 +1814,11 @@ function App() {
|
||||
};
|
||||
|
||||
const rememberMinimisedStateSoon = () => {
|
||||
window.setTimeout(() => {
|
||||
if (minimisedCheckTimer !== null) {
|
||||
window.clearTimeout(minimisedCheckTimer);
|
||||
}
|
||||
minimisedCheckTimer = window.setTimeout(() => {
|
||||
minimisedCheckTimer = null;
|
||||
if (cancelled) return;
|
||||
void rememberMinimisedState();
|
||||
}, 120);
|
||||
@@ -1895,10 +1911,6 @@ function App() {
|
||||
scheduleDevicePixelRatioCheck('resize');
|
||||
};
|
||||
|
||||
const pollTimer = window.setInterval(() => {
|
||||
void rememberMinimisedState();
|
||||
checkDevicePixelRatio();
|
||||
}, 900);
|
||||
// Windows 冷启动:WebView2 首次布局常只铺满左上角一部分,任务栏恢复才会走 restore 修复。
|
||||
// 这里在启动后主动按 startup 原因做几次轻量 settle,避免用户必须双击任务栏。
|
||||
// 间隔需大于 fixWindowScaleIfNeeded 的 700ms 节流,确保多次都能真正执行。
|
||||
@@ -1908,11 +1920,22 @@ function App() {
|
||||
void fixWindowScaleIfNeeded('startup');
|
||||
}, delayMs)
|
||||
));
|
||||
window.addEventListener('resize', handleWindowResize);
|
||||
window.addEventListener('focus', handleWindowFocus);
|
||||
window.addEventListener('blur', handleWindowBlur);
|
||||
window.addEventListener('pageshow', handlePageShow);
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
const cleanupWindowActivityScheduler = installNativeWindowActivityScheduler({
|
||||
windowTarget: window,
|
||||
documentTarget: document,
|
||||
fallbackIntervalMs: WINDOWS_SCALE_FALLBACK_INTERVAL_MS,
|
||||
onFallback: () => {
|
||||
void rememberMinimisedState();
|
||||
checkDevicePixelRatio();
|
||||
},
|
||||
handlers: {
|
||||
resize: handleWindowResize,
|
||||
focus: handleWindowFocus,
|
||||
blur: handleWindowBlur,
|
||||
pageshow: handlePageShow,
|
||||
visibilitychange: handleVisibilityChange,
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
@@ -1922,15 +1945,14 @@ function App() {
|
||||
if (resizeTimer !== null) {
|
||||
window.clearTimeout(resizeTimer);
|
||||
}
|
||||
if (minimisedCheckTimer !== null) {
|
||||
window.clearTimeout(minimisedCheckTimer);
|
||||
minimisedCheckTimer = null;
|
||||
}
|
||||
for (const timer of startupLayoutFixTimers) {
|
||||
window.clearTimeout(timer);
|
||||
}
|
||||
window.clearInterval(pollTimer);
|
||||
window.removeEventListener('resize', handleWindowResize);
|
||||
window.removeEventListener('focus', handleWindowFocus);
|
||||
window.removeEventListener('blur', handleWindowBlur);
|
||||
window.removeEventListener('pageshow', handlePageShow);
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
cleanupWindowActivityScheduler();
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -1,14 +1,182 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
installNativeWindowActivityScheduler,
|
||||
resolveTitleBarToggleIconKey,
|
||||
resolveWindowsScaleCheckDelayMs,
|
||||
shouldApplyWindowsScaleFix,
|
||||
shouldResetWebViewZoomForScaleFix,
|
||||
shouldToggleMaximisedWindowForScaleFix,
|
||||
WINDOW_STATE_FALLBACK_INTERVAL_MS,
|
||||
WINDOWS_SCALE_FALLBACK_INTERVAL_MS,
|
||||
} from './windowStateUi';
|
||||
|
||||
class FakeWindowActivityTarget {
|
||||
private readonly listeners = new Map<string, Set<EventListener>>();
|
||||
private readonly intervals = new Map<number, TimerHandler>();
|
||||
private nextTimerId = 1;
|
||||
|
||||
readonly listenerCapture = new Map<string, boolean>();
|
||||
readonly intervalDelays: number[] = [];
|
||||
|
||||
addEventListener(type: string, listener: EventListener, capture = false) {
|
||||
const listeners = this.listeners.get(type) ?? new Set<EventListener>();
|
||||
listeners.add(listener);
|
||||
this.listeners.set(type, listeners);
|
||||
this.listenerCapture.set(type, capture);
|
||||
}
|
||||
|
||||
removeEventListener(type: string, listener: EventListener) {
|
||||
this.listeners.get(type)?.delete(listener);
|
||||
}
|
||||
|
||||
setInterval(handler: TimerHandler, delayMs?: number) {
|
||||
const timerId = this.nextTimerId++;
|
||||
this.intervals.set(timerId, handler);
|
||||
this.intervalDelays.push(Number(delayMs));
|
||||
return timerId;
|
||||
}
|
||||
|
||||
clearInterval(timerId: number) {
|
||||
this.intervals.delete(timerId);
|
||||
}
|
||||
|
||||
dispatch(type: string) {
|
||||
for (const listener of this.listeners.get(type) ?? []) {
|
||||
listener(new Event(type));
|
||||
}
|
||||
}
|
||||
|
||||
tickFallbacks() {
|
||||
for (const handler of this.intervals.values()) {
|
||||
if (typeof handler === 'function') handler();
|
||||
}
|
||||
}
|
||||
|
||||
get activeIntervalCount() {
|
||||
return this.intervals.size;
|
||||
}
|
||||
}
|
||||
|
||||
class FakeDocumentActivityTarget {
|
||||
visibilityState: DocumentVisibilityState = 'visible';
|
||||
private readonly listeners = new Map<string, Set<EventListener>>();
|
||||
|
||||
addEventListener(type: string, listener: EventListener) {
|
||||
const listeners = this.listeners.get(type) ?? new Set<EventListener>();
|
||||
listeners.add(listener);
|
||||
this.listeners.set(type, listeners);
|
||||
}
|
||||
|
||||
removeEventListener(type: string, listener: EventListener) {
|
||||
this.listeners.get(type)?.delete(listener);
|
||||
}
|
||||
|
||||
dispatch(type: string) {
|
||||
for (const listener of this.listeners.get(type) ?? []) {
|
||||
listener(new Event(type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('windowStateUi', () => {
|
||||
it('keeps 10-30 seconds of visible idle native-window fallback IPC within budget', () => {
|
||||
const normalWindowStateCallsPerTick = 4;
|
||||
const windowsScaleCallsPerTick = 1;
|
||||
const countNativeIpcCalls = (idleDurationMs: number) => (
|
||||
Math.floor(idleDurationMs / WINDOW_STATE_FALLBACK_INTERVAL_MS) * normalWindowStateCallsPerTick
|
||||
+ Math.floor(idleDurationMs / WINDOWS_SCALE_FALLBACK_INTERVAL_MS) * windowsScaleCallsPerTick
|
||||
);
|
||||
|
||||
expect(countNativeIpcCalls(10_000)).toBe(1);
|
||||
expect(countNativeIpcCalls(30_000)).toBe(11);
|
||||
});
|
||||
|
||||
it('uses activity events while suppressing native fallback work on hidden pages', () => {
|
||||
const windowTarget = new FakeWindowActivityTarget();
|
||||
const documentTarget = new FakeDocumentActivityTarget();
|
||||
const onResize = vi.fn();
|
||||
const onFocus = vi.fn();
|
||||
const onPageShow = vi.fn();
|
||||
const onVisibilityChange = vi.fn();
|
||||
const onPageHide = vi.fn();
|
||||
const onBeforeUnload = vi.fn();
|
||||
const onFallback = vi.fn();
|
||||
|
||||
const cleanup = installNativeWindowActivityScheduler({
|
||||
windowTarget: windowTarget as unknown as Window,
|
||||
documentTarget: documentTarget as unknown as Document,
|
||||
fallbackIntervalMs: WINDOW_STATE_FALLBACK_INTERVAL_MS,
|
||||
onFallback,
|
||||
handlers: {
|
||||
resize: onResize,
|
||||
focus: onFocus,
|
||||
pageshow: onPageShow,
|
||||
pagehide: onPageHide,
|
||||
beforeunload: onBeforeUnload,
|
||||
visibilitychange: onVisibilityChange,
|
||||
},
|
||||
});
|
||||
expect(windowTarget.intervalDelays).toEqual([WINDOW_STATE_FALLBACK_INTERVAL_MS]);
|
||||
|
||||
windowTarget.dispatch('resize');
|
||||
windowTarget.dispatch('focus');
|
||||
windowTarget.dispatch('pageshow');
|
||||
documentTarget.dispatch('visibilitychange');
|
||||
windowTarget.dispatch('pagehide');
|
||||
windowTarget.dispatch('beforeunload');
|
||||
expect([onResize, onFocus, onPageShow, onVisibilityChange, onPageHide, onBeforeUnload]
|
||||
.map((handler) => handler.mock.calls.length))
|
||||
.toEqual([1, 1, 1, 1, 1, 1]);
|
||||
expect(windowTarget.listenerCapture.get('pagehide')).toBe(true);
|
||||
expect(windowTarget.listenerCapture.get('beforeunload')).toBe(true);
|
||||
|
||||
documentTarget.visibilityState = 'hidden';
|
||||
documentTarget.dispatch('visibilitychange');
|
||||
expect(windowTarget.activeIntervalCount).toBe(0);
|
||||
windowTarget.tickFallbacks();
|
||||
expect(onFallback).not.toHaveBeenCalled();
|
||||
|
||||
documentTarget.visibilityState = 'visible';
|
||||
documentTarget.dispatch('visibilitychange');
|
||||
expect(windowTarget.activeIntervalCount).toBe(1);
|
||||
windowTarget.tickFallbacks();
|
||||
expect(onFallback).toHaveBeenCalledTimes(1);
|
||||
|
||||
cleanup();
|
||||
windowTarget.dispatch('focus');
|
||||
documentTarget.dispatch('visibilitychange');
|
||||
windowTarget.tickFallbacks();
|
||||
expect(onFocus).toHaveBeenCalledTimes(1);
|
||||
expect(onVisibilityChange).toHaveBeenCalledTimes(3);
|
||||
expect(onFallback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('keeps Windows scale fallback dormant while the document is hidden', () => {
|
||||
const windowTarget = new FakeWindowActivityTarget();
|
||||
const documentTarget = new FakeDocumentActivityTarget();
|
||||
const onFallback = vi.fn();
|
||||
documentTarget.visibilityState = 'hidden';
|
||||
|
||||
const cleanup = installNativeWindowActivityScheduler({
|
||||
windowTarget: windowTarget as unknown as Window,
|
||||
documentTarget: documentTarget as unknown as Document,
|
||||
fallbackIntervalMs: WINDOWS_SCALE_FALLBACK_INTERVAL_MS,
|
||||
onFallback,
|
||||
handlers: {},
|
||||
});
|
||||
expect(windowTarget.intervalDelays).toEqual([]);
|
||||
|
||||
windowTarget.tickFallbacks();
|
||||
expect(onFallback).not.toHaveBeenCalled();
|
||||
documentTarget.visibilityState = 'visible';
|
||||
documentTarget.dispatch('visibilitychange');
|
||||
expect(windowTarget.intervalDelays).toEqual([WINDOWS_SCALE_FALLBACK_INTERVAL_MS]);
|
||||
windowTarget.tickFallbacks();
|
||||
expect(onFallback).toHaveBeenCalledTimes(1);
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it('does not re-toggle a maximized window on activation when focus returns', () => {
|
||||
expect(shouldToggleMaximisedWindowForScaleFix('activation', true)).toBe(false);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,96 @@ export type WindowScaleFixReason = 'activation' | 'ratio-change' | 'restore' | '
|
||||
export type WindowsScaleCheckTrigger = 'focus' | 'pageshow' | 'poll' | 'resize' | 'visibilitychange';
|
||||
export type TitleBarToggleIconKey = 'maximize' | 'restore';
|
||||
|
||||
// resize/focus/pageshow/visibility 生命周期事件承担实时同步;轮询只作为 Wails
|
||||
// 未上报“仅移动窗口”等边缘场景的低频容错,避免空闲窗口持续跨 JS/Go 边界。
|
||||
export const WINDOW_STATE_FALLBACK_INTERVAL_MS = 15_000;
|
||||
export const WINDOWS_SCALE_FALLBACK_INTERVAL_MS = 10_000;
|
||||
|
||||
type NativeWindowActivityEventHandler = () => void;
|
||||
|
||||
export interface NativeWindowActivitySchedulerHandlers {
|
||||
resize?: NativeWindowActivityEventHandler;
|
||||
focus?: NativeWindowActivityEventHandler;
|
||||
blur?: NativeWindowActivityEventHandler;
|
||||
pageshow?: NativeWindowActivityEventHandler;
|
||||
pagehide?: NativeWindowActivityEventHandler;
|
||||
beforeunload?: NativeWindowActivityEventHandler;
|
||||
visibilitychange?: NativeWindowActivityEventHandler;
|
||||
}
|
||||
|
||||
export interface NativeWindowActivitySchedulerOptions {
|
||||
windowTarget: Window;
|
||||
documentTarget: Document;
|
||||
fallbackIntervalMs: number;
|
||||
onFallback: NativeWindowActivityEventHandler;
|
||||
handlers: NativeWindowActivitySchedulerHandlers;
|
||||
}
|
||||
|
||||
export const installNativeWindowActivityScheduler = ({
|
||||
windowTarget,
|
||||
documentTarget,
|
||||
fallbackIntervalMs,
|
||||
onFallback,
|
||||
handlers,
|
||||
}: NativeWindowActivitySchedulerOptions): (() => void) => {
|
||||
const windowListeners: Array<{
|
||||
type: keyof Pick<WindowEventMap, 'resize' | 'focus' | 'blur' | 'pageshow' | 'pagehide' | 'beforeunload'>;
|
||||
listener: EventListener;
|
||||
capture: boolean;
|
||||
}> = [];
|
||||
|
||||
const addWindowListener = (
|
||||
type: keyof Pick<WindowEventMap, 'resize' | 'focus' | 'blur' | 'pageshow' | 'pagehide' | 'beforeunload'>,
|
||||
handler: NativeWindowActivityEventHandler | undefined,
|
||||
capture = false,
|
||||
) => {
|
||||
if (!handler) return;
|
||||
const listener: EventListener = () => handler();
|
||||
windowTarget.addEventListener(type, listener, capture);
|
||||
windowListeners.push({ type, listener, capture });
|
||||
};
|
||||
|
||||
addWindowListener('resize', handlers.resize);
|
||||
addWindowListener('focus', handlers.focus);
|
||||
addWindowListener('blur', handlers.blur);
|
||||
addWindowListener('pageshow', handlers.pageshow);
|
||||
addWindowListener('pagehide', handlers.pagehide, true);
|
||||
addWindowListener('beforeunload', handlers.beforeunload, true);
|
||||
|
||||
let fallbackTimer: number | null = null;
|
||||
const stopFallback = () => {
|
||||
if (fallbackTimer === null) return;
|
||||
windowTarget.clearInterval(fallbackTimer);
|
||||
fallbackTimer = null;
|
||||
};
|
||||
const startFallback = () => {
|
||||
if (fallbackTimer !== null || documentTarget.visibilityState !== 'visible') return;
|
||||
fallbackTimer = windowTarget.setInterval(() => {
|
||||
if (documentTarget.visibilityState === 'visible') {
|
||||
onFallback();
|
||||
}
|
||||
}, fallbackIntervalMs);
|
||||
};
|
||||
const visibilityListener: EventListener = () => {
|
||||
if (documentTarget.visibilityState === 'visible') {
|
||||
startFallback();
|
||||
} else {
|
||||
stopFallback();
|
||||
}
|
||||
handlers.visibilitychange?.();
|
||||
};
|
||||
documentTarget.addEventListener('visibilitychange', visibilityListener);
|
||||
startFallback();
|
||||
|
||||
return () => {
|
||||
stopFallback();
|
||||
for (const { type, listener, capture } of windowListeners) {
|
||||
windowTarget.removeEventListener(type, listener, capture);
|
||||
}
|
||||
documentTarget.removeEventListener('visibilitychange', visibilityListener);
|
||||
};
|
||||
};
|
||||
|
||||
export const shouldApplyWindowsScaleFix = (
|
||||
reason: WindowScaleFixReason,
|
||||
hasViewportScaleDrift: boolean,
|
||||
|
||||
Reference in New Issue
Block a user