Skip to content

fix(desktop): HTML 预览的“在浏览器中打开”交给默认浏览器 - #1928

Merged
xiaoyumuxi merged 5 commits into
previewfrom
fix/desktop-open-in-browser-local-file
Nov 14, 2026
Merged

xiaoyumuxi merged 5 commits into
previewfrom
fix/desktop-open-in-browser-local-file

Conversation

@xiaoyumuxi

Copy link
Copy Markdown
Collaborator

关联 Issue / Linked issue

PR 类型 / Type

  • feat:新功能或用户可见的改进
  • fix:缺陷修复
  • docs:文档、Agent Note、Skill 等规则文件
  • release:稳定版发布准备

改动说明 / What changes

背景 / Why

在 HTML 文件的预览栏点“在浏览器中打开”,总是弹出“无法在浏览器中打开文件:app: external URL must use http, https, or mailto”,浏览器不会打开。

原实现把磁盘路径拼成 file:// 地址交给 app.OpenURL,而 Viva 的 OpenURL 只接受 http、https、mailto。旧客户端(macOS MacHTMLBrowserOpener、Windows html_browser.rs)的语义是:把文件交给默认网页浏览器(打开 http 链接的那个应用),而不是 .html 的文件关联,后者常常是一个编辑器。app.OpenPath 跟随文件关联,不符合要求。按 Viva 维护规则,在 fork 里新增 API 并向上游提 PR,Lithe 应用层不写绕过代码。

改动 / Changes

“在浏览器中打开”(desktop/internal/workbench/browser_preview.go)

  • openInBrowser 改为调用 Viva 新增的 app.OpenPathInBrowser(path),把磁盘文件交给默认网页浏览器,不跟随 .html 的文件关联。有未保存的修改时仍先保存,这部分逻辑没变。
  • 调用放在后台任务里执行,结果回到窗口线程处理,不阻塞窗口线程(macOS 上要等 open 报告结果)。
  • 失败时记录 app.operation_failed(operation=editor_open_in_browser,错误经 documentLogError 处理,不带路径),并沿用原有的失败提示词条 desktop.editorMenu.openInBrowserFailed。原来失败时没有记日志。
  • app.action.performed(editor.openInBrowser)和以前一样,只在成功打开后记录。
  • 删掉拼 file:// 地址的代码和 Windows 盘符特判,以及随之不再需要的 net/url 引用。

测试

  • browser_preview_test.go:新增回归测试 TestOpenInBrowserGivesTheFileToTheDefaultBrowser。先编辑 HTML,再点预览栏按钮,断言四件事:编辑已经保存;文件交给了 Driver.LastOpenedInBrowser;既没有走 OpenPath(文件关联),也没有走 OpenURL;没有任何提示。
  • logevents_test.go:在 TestLogEventsMatchCatalog 里登记 editor_open_in_browser,确认 Core 日志目录接受这条事件。

Viva 固定版本(desktop/go.mod、desktop/go.sum)

  • replace 从 fork main 609dd47 升到 4829f2b(github.com/Lithe-IDEA/viva v0.6.3-0.20261114132607-4829f2bdde18)。require 的上游基线 b63e4a0 不变。
  • 相对当前固定版本,新增两项补丁:
fork PR 内容 上游 PR
Lithe-IDEA/Lithe-viva#17 wmSetTitle 改用 0x8053。fork #16 合入上游后,它和上游新增的 wmSetSize 撞号,Windows 编译失败(duplicate case wmSetTitle),会让本仓库的 Windows 构建同样失败 无。修的是 fork 独有的 Window.SetTitle(fork #1),上游没有这个 API
Lithe-IDEA/Lithe-viva#18 新增 app.OpenPathInBrowser、app.ErrNoBrowser、Driver.LastOpenedInBrowser vivaui/viva#272(Draft)
  • OpenPathInBrowser 在各平台找默认浏览器的方式(实现都在 fork 里,列出来方便评审):
    • macOS:NSWorkspace URLForApplicationToOpenURL:(以 https 地址为探针)找到默认浏览器,再用 /usr/bin/open -a <浏览器> <文件> 打开。
    • Windows:AssocQueryString 查 http 协议的打开命令,代入文件的 file:/// 地址后启动。
    • Linux:xdg-settings get default-web-browser,或用 xdg-mime 查 x-scheme-handler/https,找到 desktop 文件后按其 Exec 启动。
    • 没有默认浏览器时返回 app.ErrNoBrowser,界面显示失败提示。

功能矩阵(shared/platform-feature-matrix/features/embedded-viewers.json)

  • Viva 部分的验证动作写明:先保存,再把磁盘文件交给默认网页浏览器,不跟随 .html 的文件关联;把 .html 的默认打开程序改成文本编辑器后,仍在浏览器里打开;没有默认浏览器时提示失败原因;Windows 需实机确认能按 http 协议找到默认浏览器。
  • 两端证据补上 browser_preview.go 和 browser_preview_test.go;自动回归命令的 -run 加上 OpenInBrowser。

其他

不在本 PR 范围内 / Out of scope


类型专属:fix

复现与根因

  • 复现步骤、实际结果、期望结果:
    • 步骤:在工作区打开一个 .html 文件,在预览栏点“在浏览器中打开”。
    • 实际:弹出“无法在浏览器中打开文件:app: external URL must use http, https, or mailto”,浏览器不打开。
    • 期望:先保存,再在默认网页浏览器里打开磁盘上的这个文件。
  • 根因:Epic(0.7.0): 基于 Viva 框架的跨平台方案的迁移(#1794) #1799(0faecba49)迁移到 Viva 时,把文件拼成 file:// 地址交给 app.OpenURL,而 Viva 的 OpenURL 只接受 http、https、mailto(git log -S 'file://' -- desktop/internal/workbench/browser_preview.go)。
  • 影响平台:macOS、Windows、Linux,即 Viva 桌面端的所有平台,因为它们走同一份 Go 代码。

检查项

  • 已先写回归测试,确认它在修复前失败、修复后通过;测试路径与修复前的失败输出:desktop/internal/workbench/browser_preview_test.go 的 TestOpenInBrowserGivesTheFileToTheDefaultBrowser。把 browser_preview.go 换回 preview 上的版本后运行,失败输出如下:
    --- FAIL: TestOpenInBrowserGivesTheFileToTheDefaultBrowser (0.34s)
        browser_preview_test.go:40: Open in Browser gave the browser "" (false), want the file "<TMP>/site/board.html"; notices ["无法在浏览器中打开文件:app: external URL must use http, https, or mailto"]
    
    修复后通过(见“验证”)。
  • [N/A] 无法自动化时的手动步骤:已自动化。另外在 macOS 上用 fork 源码构建手动点过一次,文件在默认浏览器(Chrome)中打开(见“验证”)。
  • 已搜索被修改函数、类型、契约字段的全部调用方:
    • openInBrowser 只由预览栏按钮调用。
    • openURL / app.OpenURL 的其他调用方(Markdown 和悬停链接、设置页链接、终端链接、插件 browser/openExternal)传的都是 http、https 或 mailto 地址,不受影响。
    • workbench 里已经没有其他把 file:// 交给 openURL 的地方:document_links.go 的 file:// 目标是在编辑器里打开。
  • 已检查另一平台是否有同类缺陷:两端走同一份 Go 代码,本 PR 一并修复;Viva 的 Windows 实现已在 fork CI 的 native (windows-latest) 上编译并通过测试,实机验证见“不在本 PR 范围内”。
  • 只改了修复所需的代码,没有夹带重构。固定版本升级和矩阵更新是这个修复所必需的;发现的其他问题都是 fork 或上游的事,列在“不在本 PR 范围内”。
  • [N/A] 在 .agents/notes/implemented/bug-fix/ 记录:“交给默认浏览器,不跟随文件关联”这条约束已经写进代码注释和功能矩阵的验证动作,回归测试也同时断言不走 OpenPath、不走 OpenURL,复发会被测试拦住。

范围检查 / Scope checks

功能矩阵 / Feature matrix

  • 已更新 shared/platform-feature-matrix/features/embedded-viewers.json:Viva 部分的验证动作、两端证据和自动回归命令;两端的实现和验证状态如实保持 partial / pending。
  • 已运行 ./scripts/platform-feature-matrix/verify-platform-feature-matrix.sh(189 项能力通过)和 ./scripts/platform-feature-matrix/verify-platform-feature-matrix-change.sh origin/preview HEAD(通过)。
  • 没有提交 .artifacts/ 下生成的矩阵视图。
  • [N/A] 没有更新矩阵的理由:已更新。

插件归属 / Plugin ownership

  • 已在仓库根目录运行 develop-lithe 第三节“插件归属与宿主零依赖”的自查命令,rg 结果为空。
  • 新增或修改的测试没有涉及插件。
  • 没有为具体插件的能力新建或更新宿主功能矩阵条目。
  • 没有插件文案或插件构建资源。
  • [N/A] 修改了通用扩展点:没有改。插件的 browser/openExternal 行为不变。

统一日志系统 / Structured logging

  • 已接入统一日志系统:失败时用 Core 目录中已声明的 app.operation_failed,经 operationFailedFields 记录;成功时沿用已有的 app.action.performed。TestLogEventsMatchCatalog 开着目录校验,确认这两条都能被接受。
  • 一个操作只有一条终态:成功记录 app.action.performed,失败记录 app.operation_failed;错误只在窗口线程的结果回调里记录一次。
  • 没有记录路径或内容:错误经 documentLogError 处理,去掉了路径。
  • 旧日志生成和使用校验脚本(scripts/logging/)已移除,等价检查还没恢复,所以日志门禁记为未完成。目前只有上面的目录校验测试。

界面文案 / i18n

  • 没有新增界面文案,沿用已有词条 desktop.editorMenu.openInBrowserFailed。
  • [N/A] 插件提供的文字:不涉及。
  • 已运行 node scripts/i18n/verify-app-language-localization.mjs(通过)和 node scripts/i18n/generate-i18n.mjs --check(中英 11147/11147 完整)。

逻辑归属 / Logic placement

  • [N/A] 可共享逻辑:没有新增业务规则。找默认浏览器并启动它是平台原生 I/O,在 Viva 的平台层实现。
  • [N/A] 平台层已有的可共享逻辑:不涉及。
  • 宿主和 Core 没有按工具名加分支。

性能与内存 / Performance and memory

  • [N/A] 后台功能、子进程、缓存、定时器:没有新增常驻资源。只在用户点击时启动一次浏览器进程,由 Viva 负责回收。
  • 主线程不启动进程:打开浏览器从窗口线程移到了后台任务里执行。
  • [N/A] 性能基线:不涉及启动、输入、滚动、大文件和搜索。

设置 / Settings

N/A:没有改设置。

插件契约 / Plugin contract

N/A:没有改契约、宿主方法或 Core 固定版本。

发行包与资源 / Bundle and resources

N/A:没有新增运行时资源。仍然运行了 ./scripts/desktop/verify/verify-runtime-bundle-immutability.sh,macOS 和 Windows 源码的静态检查都通过。

无用代码与重构 / Dead code and refactoring

  • 拼 file:// 地址、Windows 盘符特判和 net/url 引用,在本次改动后都没用了,已在本 PR 删除。
  • [N/A] 既有无用代码:没有发现与本次改动相关的。

验证 / Verification

命令或操作 平台 结果
./scripts/desktop/desktop-go.sh test ./...(固定版本指向与 4829f2b 同一棵树的 a788c6c,两者 tree 都是 939d8de) macOS arm64 通过,55 个包 ok
./scripts/desktop/desktop-go.sh test ./internal/workbench -count=1(调整 performed 位置后,固定版本 4829f2b) macOS arm64 通过
./scripts/desktop/desktop-go.sh test ./internal/workbench -race -run 'OpenInBrowser|HTML|LogEventsMatchCatalog|Viewer|SVG' -count=1 macOS arm64 通过
回归测试的修复前失败(browser_preview.go 换回 preview 版本) macOS arm64 按预期失败,输出见上
./scripts/desktop/desktop-go.sh vet ./internal/workbench、gofmt -l macOS arm64 通过,没有格式差异
./scripts/desktop/desktop-go.sh --exec go run github.com/vivaui/viva/cmd/viva vet ./... macOS arm64 通过,只有链接器的重复库警告
./scripts/desktop/generate-desktop-icons.sh、./scripts/desktop/generate-desktop-file-icons.sh macOS arm64 重新生成后与已提交的 icons.go、icons_tree.go 逐字节一致
./.agents/skills/write-stable-tests/scripts/verify-test-stability.sh macOS 通过
./scripts/agent-notes/verify-agent-notes.sh macOS 通过(224 份)
两个 i18n 检查、两个功能矩阵检查、verify-runtime-bundle-immutability.sh macOS 通过
fork 上 go vet(windows/amd64、windows/arm64、linux/amd64、darwin)和 go test ./app ./internal/platform/...(#17 + #18 合并后) macOS 交叉编译 通过
fork CI(Lithe-IDEA/Lithe-viva#18,a788c6c):native(macOS、Ubuntu、Windows)、cross-vet、windows-arm64-text、darwin、lint、test GitHub Actions 通过;size、web、windows-materials 失败,原因与 fork main、上游 main 相同
手动:用 fork 源码构建,在 HTML 预览栏点“在浏览器中打开” macOS 文件在默认浏览器(Chrome)中打开

未能执行的检查和原因:

  • Windows 实机验证(默认浏览器查找,中文、空格、# 文件名,没有默认浏览器时的提示):当前机器是 macOS,没有在 Windows 上运行。
  • 改用 4829f2b 后没有在整机里重新手动点击:它的代码树与手动验证时一致,但手动验证用的是本地 fork 源码。
  • 日志门禁:旧校验脚本已移除,等价检查还没恢复,记为未完成。

提交前确认 / Final checks

  • 分支从最新的 preview 创建,并合入了最新的 preview;目标分支:preview
  • 以 Draft 提交,本地验证通过后才标记 ready
  • 没有无关改动,没有提交 .build/、target/、dist/、.artifacts/ 等产物,没有凭据、个人路径和未脱敏的日志或截图
  • 本次构建、测试或预览启动的 Lithe 应用和子进程已全部关闭,没有进程残留

预览栏的“在浏览器中打开”原来拼出 file:// 地址交给 app.OpenURL,
Viva 只接受 http、https、mailto,于是总是报错。改为调用 Viva 新增的
app.OpenPathInBrowser:交给默认浏览器(打开 http 链接的应用),
不跟随 .html 的文件关联,与旧客户端一致。

- 在后台调用,macOS 上要等 open 报告结果,不阻塞窗口线程。
- 失败时记录 app.operation_failed(operation=editor_open_in_browser),
  并在日志目录测试里登记。
- 回归测试断言文件交给了 LastOpenedInBrowser,既不是 OpenPath 也不是 OpenURL。

依赖 Lithe-IDEA/Lithe-viva#18(上游 vivaui/viva#272)合入 fork main
后升级 desktop/go.mod 的固定版本,在此之前本分支编译不过。
desktop/go.mod 的 replace 从 fork main 609dd47 升到 4829f2b,
多出两项补丁:

- Lithe-IDEA/Lithe-viva#17:wmSetTitle 改用 0x8053,修复 #16 合入上游
  后与 wmSetSize 撞号导致的 Windows 编译失败。补的是 fork 独有的
  Window.SetTitle,上游没有对应 PR。
- Lithe-IDEA/Lithe-viva#18:新增 app.OpenPathInBrowser,上游 PR 为
  vivaui/viva#272。

require 仍是上游基线 b63e4a0,没有变化。

功能矩阵 embedded-viewers:Viva 的“在浏览器中打开”写明交给默认网页
浏览器、不跟随 .html 的文件关联,并注明 Windows 需实机确认;证据补上
browser_preview.go 及其测试,自动回归的 -run 加上 OpenInBrowser。
与设置页链接和修复前的行为一致:打开失败时只记录 app.operation_failed 和提示,不再同时记一条已执行的操作。
@ghfind-review ghfind-review Bot added the review: high ghfind author score; see https://ghfind.com label Nov 14, 2026
@xiaoyumuxi
xiaoyumuxi marked this pull request as ready for review November 14, 2026 13:38
@xiaoyumuxi
xiaoyumuxi merged commit b08c3ec into preview Nov 14, 2026
5 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review: high ghfind author score; see https://ghfind.com

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant