mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-04 21:07:45 +08:00
🐛 fix(window): 直接调 WebView2 zoom factor 零感知修复 Windows 字体异常
- 新增 ResetWebViewZoom RPC:从 ctx 反射拿 Wails 内部 *edge.Chromium,调 PutZoomFactor(1.0) 强制 WebView2 重算 D2D/DirectWrite 字体度量,完全不动窗口零动画
- 自动路径:maximised + restore + drift 时直接调 backend reset,告别 9848b8b2 之后字体偶发变大的取舍
- 手动路径:保留 Ctrl+Shift+0 快捷键作为兜底(优先 WebView2 reset,失败回退 toggle)
- 撤回上一版 CSS zoom nudge:实测在 WebView2 上不能修字体度量(度量缓存在 D2D 层不在 Chromium layout 层)
- 反射做了 3 层签名校验(frontend value / chromium 字段 / PutZoomFactor 方法签名),wails 升级破坏接口时返回 error 不让进程崩溃
- 新增 windows-only 反射逻辑测试 4 个、跨平台 RPC 行为测试 2 个、Ctrl+Shift+0 快捷键注册测试
This commit is contained in:
@@ -128,6 +128,19 @@ describe('shortcut defaults', () => {
|
||||
scope: 'queryEditor',
|
||||
});
|
||||
});
|
||||
|
||||
// Windows 任务栏恢复后字体异常变大的兜底入口(方案 3)。
|
||||
// 自动 fix 路径(9848b8b2)刻意不再 toggle 以避免可见动画,由该快捷键给用户主动触发的修复入口。
|
||||
it('registers reset window zoom shortcut with default Ctrl+Shift+0', () => {
|
||||
expect(DEFAULT_SHORTCUT_OPTIONS.resetWindowZoom).toEqual({
|
||||
combo: 'Ctrl+Shift+0',
|
||||
enabled: true,
|
||||
});
|
||||
expect(SHORTCUT_ACTION_META.resetWindowZoom).toMatchObject({
|
||||
label: '重置窗口缩放',
|
||||
allowInEditable: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ─── comboToMonacoKeyBinding ─────────────────────────────────────────
|
||||
|
||||
@@ -9,7 +9,8 @@ export type ShortcutAction =
|
||||
| 'toggleLogPanel'
|
||||
| 'toggleTheme'
|
||||
| 'openShortcutManager'
|
||||
| 'toggleMacFullscreen';
|
||||
| 'toggleMacFullscreen'
|
||||
| 'resetWindowZoom';
|
||||
|
||||
export interface ShortcutBinding {
|
||||
combo: string;
|
||||
@@ -87,6 +88,7 @@ export const SHORTCUT_ACTION_ORDER: ShortcutAction[] = [
|
||||
'toggleTheme',
|
||||
'openShortcutManager',
|
||||
'toggleMacFullscreen',
|
||||
'resetWindowZoom',
|
||||
];
|
||||
|
||||
export const SHORTCUT_ACTION_META: Record<ShortcutAction, ShortcutActionMeta> = {
|
||||
@@ -135,6 +137,11 @@ export const SHORTCUT_ACTION_META: Record<ShortcutAction, ShortcutActionMeta> =
|
||||
description: 'macOS 原生窗口控制模式下的全屏切换(⌃⌘F)',
|
||||
platformOnly: 'mac',
|
||||
},
|
||||
resetWindowZoom: {
|
||||
label: '重置窗口缩放',
|
||||
description: 'Windows 任务栏恢复后字体异常变大时主动触发;会切一次最大化让 WebView2 重算字体度量',
|
||||
allowInEditable: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const DEFAULT_SHORTCUT_OPTIONS: ShortcutOptions = {
|
||||
@@ -147,6 +154,7 @@ export const DEFAULT_SHORTCUT_OPTIONS: ShortcutOptions = {
|
||||
toggleTheme: { combo: 'Ctrl+Shift+D', enabled: true },
|
||||
openShortcutManager: { combo: 'Ctrl+,', enabled: true },
|
||||
toggleMacFullscreen: { combo: 'Ctrl+Meta+F', enabled: true },
|
||||
resetWindowZoom: { combo: 'Ctrl+Shift+0', enabled: true },
|
||||
};
|
||||
|
||||
const normalizeKeyToken = (value: string): string => {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
// @vitest-environment jsdom
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
applyWindowsViewportZoomNudge,
|
||||
computeWindowsViewportScaleRatio,
|
||||
getWindowsScaleFixNudgedWidth,
|
||||
hasWindowsViewportScaleDrift,
|
||||
@@ -44,32 +42,4 @@ describe('windowsScaleFix', () => {
|
||||
expect(getWindowsScaleFixNudgedWidth(960)).toBe(959);
|
||||
expect(getWindowsScaleFixNudgedWidth(420)).toBe(421);
|
||||
});
|
||||
|
||||
it('applies and resets the CSS zoom nudge across two animation frames', () => {
|
||||
const rafCallbacks: FrameRequestCallback[] = [];
|
||||
const rafSpy = vi.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => {
|
||||
rafCallbacks.push(cb);
|
||||
return rafCallbacks.length;
|
||||
});
|
||||
|
||||
const root = document.documentElement;
|
||||
const style = root.style as CSSStyleDeclaration & { zoom?: string };
|
||||
style.zoom = '';
|
||||
|
||||
applyWindowsViewportZoomNudge();
|
||||
// 第一阶段:zoom 已被设置为 1.0001
|
||||
expect(style.zoom).toBe('1.0001');
|
||||
expect(rafCallbacks).toHaveLength(1);
|
||||
|
||||
// 第一帧:调度第二帧 reset
|
||||
rafCallbacks[0]?.(0);
|
||||
expect(style.zoom).toBe('1.0001');
|
||||
expect(rafCallbacks).toHaveLength(2);
|
||||
|
||||
// 第二帧:恢复 zoom
|
||||
rafCallbacks[1]?.(0);
|
||||
expect(style.zoom).toBe('');
|
||||
|
||||
rafSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,34 +44,3 @@ export const getWindowsScaleFixNudgedWidth = (width: number): number => {
|
||||
}
|
||||
return normalizedWidth > 480 ? normalizedWidth - 1 : normalizedWidth + 1;
|
||||
};
|
||||
|
||||
// applyWindowsViewportZoomNudge 通过短暂改变 documentElement 的 CSS zoom 触发 Chromium
|
||||
// 重新计算 layout metrics。用于 maximised 窗口在 restore 场景下 viewport drift 修复的
|
||||
// 无动画路径:不重新最大化(避免 9848b8b2 修复的可见"重复最大化"抖动),也不调 WebView2
|
||||
// COM API(避免 Windows 平台特定代码)。
|
||||
//
|
||||
// 为什么这样能修:Chromium 在切换 zoom factor 时会重算所有 px 度量与 currentColor/font-size
|
||||
// 派生值,drift 后残留的旧度量被丢弃。1.0001 与 1 在视觉上不可分辨但属于 invalidation 阈值
|
||||
// 之外,强制触发完整 layout 重排。
|
||||
//
|
||||
// 用 requestAnimationFrame 两帧而不是立即 reset,让 Chromium 在第一帧完成 nudge layout、
|
||||
// 第二帧恢复——避免单帧合成被合并掉。
|
||||
export const applyWindowsViewportZoomNudge = (): void => {
|
||||
if (typeof document === 'undefined' || !document.documentElement) {
|
||||
return;
|
||||
}
|
||||
const root = document.documentElement;
|
||||
const style = root.style as CSSStyleDeclaration & { zoom?: string };
|
||||
const previousZoom = style.zoom ?? '';
|
||||
style.zoom = '1.0001';
|
||||
const reset = () => {
|
||||
style.zoom = previousZoom;
|
||||
};
|
||||
if (typeof window === 'undefined' || typeof window.requestAnimationFrame !== 'function') {
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
window.requestAnimationFrame(() => {
|
||||
window.requestAnimationFrame(reset);
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user