🐛 fix(window): 修复 Windows 恢复窗口后字体模糊

- Windows 从任务栏恢复窗口时直接触发 WebView2 zoom reset
- restore 场景不再依赖 viewport ratio drift 判断
- 覆盖最大化和非最大化窗口的恢复修复路径
- 保留最大化窗口零动画修复,避免二次最大化抖动
- 补充窗口恢复策略和 App 自动修复路径测试
This commit is contained in:
Syngnat
2026-06-02 14:04:00 +08:00
parent c72542c92c
commit e4a8c53079
5 changed files with 31 additions and 8 deletions

View File

@@ -1 +1 @@
d0464f9da25e9356e61652e638c99ffe
0295a42fd931778d85157816d79d29e5

View File

@@ -183,6 +183,14 @@ describe('tool center menu entries', () => {
expect(appSource).toContain('switchActiveTabByOffset, themeMode');
});
it('automatically resets WebView2 zoom when a Windows taskbar restore returns focus', () => {
expect(appSource).toContain('shouldResetWebViewZoomForScaleFix(reason, hasViewportScaleDrift)');
expect(appSource).toContain('const shouldResetWebViewZoom = shouldResetWebViewZoomForScaleFix(reason, hasViewportScaleDrift);');
expect(appSource).toContain('if (shouldResetWebViewZoom && !isMaximised)');
expect(appSource).toContain('const res = await (window as any).go?.app?.App?.ResetWebViewZoom?.();');
expect(appSource).toContain('该异常不一定表现为 viewport ratio drift');
});
it('keeps titlebar double-click on maximise while shortcuts may enter macOS fullscreen', () => {
expect(appSource).toContain('const handleTitleBarWindowToggle = async (options?: { allowMacNativeFullscreen?: boolean }) => {');
expect(appSource).toContain('const allowMacNativeFullscreen = options?.allowMacNativeFullscreen === true;');

View File

@@ -883,15 +883,28 @@ function App() {
devicePixelRatio: Number(window.devicePixelRatio) || 1,
visualViewportScale: window.visualViewport?.scale,
});
const shouldResetWebViewZoom = shouldResetWebViewZoomForScaleFix(reason, hasViewportScaleDrift);
if (shouldResetWebViewZoom && !isMaximised) {
try {
const res = await (window as any).go?.app?.App?.ResetWebViewZoom?.();
if (!res?.success) {
console.warn('ResetWebViewZoom unavailable in fixWindowScaleIfNeeded:', res?.message);
}
} catch (e) {
console.warn('ResetWebViewZoom call failed in fixWindowScaleIfNeeded', e);
}
}
if (isMaximised) {
if (!shouldToggleMaximisedWindowForScaleFix(reason, hasViewportScaleDrift)) {
// restore + drift(任务栏点击恢复后字体异常变大)的零感知修复路径:
// restore任务栏点击恢复后字体异常变大/变糊)的零感知修复路径:
// 调 backend App.ResetWebViewZoom 触发 WebView2 ICoreWebView2Controller::put_ZoomFactor(1.0)
// 让 WebView2 重算 D2D/DirectWrite 字体度量。完全不动窗口、零动画。
// 让 WebView2 重算 D2D/DirectWrite 字体度量。该异常不一定表现为 viewport ratio drift
// 所以 restore 场景不能依赖 hasViewportScaleDrift。完全不动窗口、零动画。
// backend 失败wails 升级破坏反射 / 非 Windows时回退到 dispatch resize 兜底;
// 用户仍可按 Ctrl+Shift+0 手动 toggle 修复。
if (shouldResetWebViewZoomForScaleFix(reason, hasViewportScaleDrift)) {
if (shouldResetWebViewZoom) {
try {
const res = await (window as any).go?.app?.App?.ResetWebViewZoom?.();
if (!res?.success) {

View File

@@ -27,9 +27,11 @@ describe('windowStateUi', () => {
expect(shouldToggleMaximisedWindowForScaleFix('restore', true)).toBe(false);
});
it('only calls the backend WebView2 zoom reset after a real restore drift', () => {
it('calls the backend WebView2 zoom reset whenever a minimized window is restored', () => {
expect(shouldResetWebViewZoomForScaleFix('restore', true)).toBe(true);
expect(shouldResetWebViewZoomForScaleFix('restore', false)).toBe(false);
// 字体模糊/DirectWrite 度量缓存异常不一定表现为 viewport ratio drift
// 因此任务栏恢复场景必须直接走零动画 WebView2 zoom reset。
expect(shouldResetWebViewZoomForScaleFix('restore', false)).toBe(true);
expect(shouldResetWebViewZoomForScaleFix('activation', true)).toBe(false);
expect(shouldResetWebViewZoomForScaleFix('ratio-change', true)).toBe(false);
});

View File

@@ -22,8 +22,8 @@ export const shouldToggleMaximisedWindowForScaleFix = (
export const shouldResetWebViewZoomForScaleFix = (
reason: WindowScaleFixReason,
hasViewportScaleDrift: boolean,
): boolean => reason === 'restore' && hasViewportScaleDrift;
_hasViewportScaleDrift: boolean,
): boolean => reason === 'restore';
export const resolveWindowsScaleCheckDelayMs = (trigger: WindowsScaleCheckTrigger): number =>
trigger === 'resize' ? 240 : 0;