Files
Haocode/docs/agent-handoff/evidence/P1-03.md
T
2026-09-17 16:30:02 +08:00

9.4 KiB
Raw Blame History

P1-03 证据:Windows/Linux 渲染器启动链

日期:2026-07-17 · 执行环境:Windows 11 10.0.26200 x64 / CPython 3.10.21 (.venv, uv) / PyQt6 6.10 + PyQt6-WebEngine 6.10 · 每条命令均带显式超时

改动文件

文件 性质 说明
core/renderer_backend.py 新增(仅 stdlib,可在导入 PyQt6 前使用) resolve_backend(pref)auto/webview2/qtwebengine 归一化;非法值/平台不支持 → 可见警告 + 平台默认,绝不阻断启动);platform_default_backend()webview2_module()win32 门控的按需导入);webengine_profile_name()/webengine_profile_dir()(每实例独立 profile 目录:源码运行 data/webengine/profile_<pid>_<ms>,测试经 HAOCODE_WEBENGINE_PROFILE_DIR 重定向临时目录;只创建、从不清理);is_root_or_container()geteuid==0 / .dockerenv / .containerenv / .lxc / /proc/1/cgroup);sanitize_chromium_flags()--no-sandbox 契约)
main.py 修改 sys.path 就绪后、导入 MainWindow 前调用 sanitize_chromium_flags 并打印最终 QTWEBENGINE_CHROMIUM_FLAGS
ui/views/main_window.py 修改(手术式) core.webview2 导入改为 if sys.platform == "win32" 门控(Linux 永不导入,不触达 pythonnet/Win32/WebView2 DLL/taskkill);② 浏览器创建处先 resolve_backend(config["webview_backend"]) 并打印警告(原「读取配置」从 win32 分支内提前到分支外,Linux 非法值也有可见警告);③ QtWebEngine 路径创建本实例 QWebEngineProfilesetPersistentStoragePath/setCachePath 指向独立目录),CustomWebPage(profile, browser)
ui/views/custom_web_page.py 修改 构造函数兼容 CustomWebPage(profile, parent) 与旧式 CustomWebPage(parent)(按 isinstance(QWebEngineProfile) 分派,防旧调用把 view 误当 profile
requirements.txt 修改 pythonnet==3.1.0; sys_platform == "win32"clr_loader==0.3.1; sys_platform == "win32";追加 Linux 源码运行最小步骤(apt 系统库清单、root/容器 --no-sandbox 说明、离屏测试命令)
tests/_test_env.py 修改 isolate() 增加 HAOCODE_WEBENGINE_PROFILE_DIR → 临时目录(setdefault,可覆盖)
tests/smoke_offscreen.py 修改 同上(该文件不走 isolate
tests/test_renderer_matrix.py 新增 R1/R3/R4/R5(见下)19 项
docs/agent-handoff/PLATFORM_PLAN.md 修改 「Linux 与 QtWebEngine 回落」追加 Linux 源码运行最小步骤 + 实现锚点

vendor/webview2/ 与根 WebView2Loader.dll 未动;未新增产品功能/安装器/PyInstaller 改动。

关键设计决策与踩坑记录

  1. QTimer.singleShot 单位是毫秒R4 首版写 QTimer.singleShot(25, finish) 期望 25s 兜底,实际 25ms 就触发 → 两个 worker 都报 loaded=Falserc=1)。诊断时手动复现看到 BACKSTOP t=0.4(30ms 定时器在事件循环启动 ~0.35s 后即触发),定位后改为 30000
  2. QtWebEngineWidgets 必须先于 QApplication 导入(否则 ImportErrorQtWebEngineWidgets must be imported ... before a QCoreApplication instance is created);QWebEngineProfile 必须先于使用它的 page/view 创建setPersistentStoragePath/setCachePath 必须在 profile 使用前调用。探针脚本先后踩中这两个顺序问题(前者 ImportError;后者在 QApplication 前建 profile 直接进程被杀 exit 127、stdout 缓冲丢失)。
  3. PyQt6-WebEngine 6.10 无 QWebEnginePage.errorOccurredQt 6.5+ API 未在此绑定暴露)→ 诊断改用 loadFinished(ok) + 手动 processEvents 循环对比三种 profile 配置(默认 profile / 命名 profile 无自定义路径 / 命名 profile + 自定义路径),三者 file:// 加载全部成功,证明「命名 profile + setPersistentStoragePath」组合可用。
  4. 旧式位置调用兼容CustomWebPage(browser) 的 view 会被新签名当作 profile 传入 → TypeError: argument 1 has unexpected type 'QWebEngineView'(smoke 前用探针暴露)。按类型分派(isinstance(QWebEngineProfile))同时支持新旧调用。
  5. R3 Linux 模拟导入:子进程 sys.platform='linux'import ui.views.main_window 成功——因 global_hotkey.pyfrom ctypes import wintypes 只是类型定义(3.10 下跨平台可导入),ctypes.windll 访问全在 if _is_windows 内;core.webview2 模块级仅 stdlib 导入,但被 main_window 的 win32 门控挡住,clr/clr_loader 未进 sys.modules

验证结果(全部显式超时)

检查 结果 命令(超时)
tests/test_renderer_matrix.py 19/19 PASS PYTHONIOENCODING=utf-8 python tests/test_renderer_matrix.pytimeout 300
tests/test_wv2_guard.pyP0 遗留守卫) 10/10 PASS timeout 120
tests/test_debug_window.py 22 PASS / 0 FAIL timeout 180
tests/smoke_offscreen.py 8/8 ALL PASS,日志含 [Renderer] QtWebEngine 独立 profile: <temp>/profile_<pid>_* QT_QPA_PLATFORM=offscreen HAOCODE_RENDER=software QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu timeout 240
node tests/test_math_extract.js 39 passed, 0 failed timeout 60
main.py 真实启动链(offscreen + 临时配置,无 providers 启动成功、无 traceback[渲染] 最终 QTWEBENGINE_CHROMIUM_FLAGS = '--disable-gpu'[Renderer] QtWebEngine 独立 profile: D:\...\data\webengine\profile_56256_*[System] 浏览器内核: QtWebEnginedata/config.json mtime 前后一致(未触碰) HAOCODE_CONFIG_FILE=<temp> QT_QPA_PLATFORM=offscreen ... timeout 45 python main.py124=到点 kill,预期)
main.py 真实启动链:普通桌面 + --no-sandbox 标志被剥离 + 告警:[Renderer] ⚠️ ... 已剥离该标志并保留 Chromium 沙箱,最终 flags 无 --no-sandbox 同上(timeout 30
回归:tests/run_tests.py 41 passed timeout 300
回归:test_bash_stream / test_tool_params / test_main_window_event_filter / test_config_isolation 全部 ALL PASS / EXIT=0 timeout 300
回归:node tests/test_render_window.js 424 passed, 0 failed timeout 120
回归:tests/smoke_mode.py ALL PASS timeout 240
P0-03 一致性抽查(文件存在、requirements marker、wv2 门控、单提示词占位符) 10/10 纯 Python 读文件

测试点明细(test_renderer_matrix.py

  • R1.1R1.11 resolve_backend 矩阵(mock 平台):Win × auto/webview2/qtwebengine/非法字符串/非字符串/大小写容差;Linux × auto/webview2(警告+回落)/qtwebengine/非法;webview2_module() 非 Windows 返回 None。
  • R3.1 子进程 sys.platform='linux' 导入 ui.views.main_window_wv2mod is Nonecore.webview2/clr/clr_loader 均不在 sys.modules
  • R4.1R4.3 两个 offscreen 子进程并行各建 QWebEngineProfileHAOCODE_WEBENGINE_PROFILE_DIR 同基目录、各自 parallel_<pid> 子目录)+ CustomWebPage(profile, view) 载入本地 HTML:均 WORKER_OK loaded=True;两 profile 目录不同;进程退出后目录仍在(无互相清理)。
  • R5.1R5.4 sanitize_chromium_flags 契约(mock root/容器探测 + 捕获 stdout):普通桌面无标志原样;普通桌面剥离 --no-sandbox+告警;root/容器+显式保留+「高可见警告」;root/容器未设→原样+提示。

遗留 / 平台验证待办

  • Linux 真机验证(待 Ubuntu 主机)pip install -r requirements.txt(确认 pythonnet/clr_loader 被 marker 跳过)→ python3.10 main.pyX11/Wayland+ 离屏套件 + tests/test_renderer_matrix.py 的 R3/R4(真实 Linux 平台而非模拟)。
  • root/容器真机--no-sandbox 接受路径的运行时验证(本环境为普通 Windows 桌面,只能验证剥离路径;接受路径为纯逻辑 + mock 验证)。
  • Windows 真机 WebView2 首选路径:已验证(2026-09-17 真桌面)。本机 D: 卷 .NET 把工程卷误判为“网络位置”导致 clr.AddReferenceLoadFrom)报 0x80131515;在 core/webview2.pybyte[] 回落(快路径仍 AddReference,失败才 Assembly.Load(byte[]),正常机器行为不变;外层异常改 BaseException 以兼容 pythonnet 非 Exception 异常)。修复后真桌面启动日志:[WV2] AddReference 路径加载失败 → 回落 Assembly.Load(byte[])Runtime ready: 153.0.4234.32controller readyNavigationCompleted src=file:///.../ui/web/index.htmlstderr 无 error/disposed/0x8007。test_wv2_guard 10/10 无回归。
  • Windows 真机 QtWebEngine 回落:已验证(2026-09-17 真桌面,HAOCODE_FORCE_QTWEBENGINE=1。日志:[WV2] ... 跳过 WebView2,回落 QtWebEngine(强制标志生效,未执行 taskkill)→ [Renderer] QtWebEngine 独立 profile: data/webengine/profile_<pid>_<ms>per-instance 隔离 profile 真机生效)→ [System] 浏览器内核: QtWebEngine。聊天区截图像素统计:83% 亮背景 + 159 种颜色桶(含文本深色像素)= 真实 DOM 渲染,非空白。两模式主窗口截图已存 evidence:win_real_wv2_mainwindow.pngwin_real_qtwebengine_mainwindow.png(半尺寸 PNG)。
    • 交叉验证(T0 守卫真机):WV2 主程序运行期间另跑 diag_panel_scrollbar.pydiag 实例因 instance-lock 被占自动回落 QtWebEngine,未误杀主程序 WebView2 进程。