Files
MyGoNavi/internal/app/window_zoom_windows.go
Syngnat 2580e4d6f3 🐛 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 快捷键注册测试
2026-05-15 16:01:18 +08:00

68 lines
2.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//go:build windows
package app
import (
"context"
"fmt"
"reflect"
"unsafe"
)
// resetWebViewZoomFactor 通过 WebView2 ICoreWebView2Controller::put_ZoomFactor 把 WebView2
// 内部 zoom factor 重置为 1.0。这是 Windows 任务栏恢复后字体度量异常变大的根因解:
// 字体度量缓存在 WebView2 D2D/DirectWrite 层Chromium layout invalidationCSS zoom hack
// 改不了它,必须调 WebView2 COM API。
//
// 实现路径:
// 1. Wails 在 ctx 里以 key "frontend" 注入了 *desktop/windows.Frontend
// 2. Frontend.chromium 是 unexported 字段 *edge.Chromium
// 3. Chromium.PutZoomFactor(float64) 是 exported 方法(封装了 controller.put_ZoomFactor
//
// 用反射 + unsafe.Pointer 解锁 unexported 字段后 MethodByName("PutZoomFactor").Call。
// 不需要 import wails 内部包,也不需要 fork wails。
//
// 失败时返回错误(不 panic让调用方决定是否回退到 toggle 路径。
//
// **依赖 wails v2.11/v2.12 内部实现细节**:如果 wails 升级改名了 frontend.chromium 字段或
// edge.Chromium.PutZoomFactor 方法名,此函数会返回 error。CI 中应该有跨版本兼容性测试。
func resetWebViewZoomFactor(ctx context.Context, factor float64) error {
if ctx == nil {
return fmt.Errorf("ctx is nil")
}
frontendIface := ctx.Value("frontend")
if frontendIface == nil {
return fmt.Errorf("wails frontend not found in ctx (key=\"frontend\")")
}
frontendValue := reflect.ValueOf(frontendIface)
if frontendValue.Kind() == reflect.Ptr {
frontendValue = frontendValue.Elem()
}
if !frontendValue.IsValid() || frontendValue.Kind() != reflect.Struct {
return fmt.Errorf("wails frontend has unexpected kind %v", frontendValue.Kind())
}
chromiumField := frontendValue.FieldByName("chromium")
if !chromiumField.IsValid() {
return fmt.Errorf("wails Frontend.chromium field not found (wails version may have changed)")
}
if chromiumField.IsNil() {
return fmt.Errorf("wails Frontend.chromium is nil (WebView2 not yet initialised)")
}
// 用 NewAt + unsafe.Pointer 解锁 unexported 字段访问限制
accessible := reflect.NewAt(chromiumField.Type(), unsafe.Pointer(chromiumField.UnsafeAddr())).Elem()
method := accessible.MethodByName("PutZoomFactor")
if !method.IsValid() {
return fmt.Errorf("PutZoomFactor method not found on chromium (go-webview2 version may have changed)")
}
if method.Type().NumIn() != 1 || method.Type().In(0).Kind() != reflect.Float64 {
return fmt.Errorf("PutZoomFactor signature changed: expected func(float64), got %v", method.Type())
}
// PutZoomFactor 内部已经 swallow error 并通过 errorCallback 报告——这里不会 panic
method.Call([]reflect.Value{reflect.ValueOf(factor)})
return nil
}