网页本地能力测试仪
Web Native Capability Tester —— 一个用来摸清「Web 标准 API」与「原生能力」在各平台上真实边界的测试脚手架。
同一份 Vue 3 前端,既能直接跑在浏览器里,也能装进 Tauri v2 壳里跑,用左右对照的方式回答同一个问题:
这个能力,到底能不能用?权限怎么弹?报什么错?哪个平台不行?
目录
1. 项目定位
这是一个测试工具,不是产品,也不追求 UI 完备。它只做一件事:把每个能力单独隔离出来,跑一遍,把结果摊在屏幕上。
三个明确目标:
- 对照 —— 同一个需求,分别用 Web 标准 API 和 Tauri 原生 IPC 实现一遍,观察差异(路径、权限、错误码)。
- 取证 —— 所有操作、错误、返回值实时记入日志,可一键导出 JSON,作为一份可归档的边界报告。
- 复用 —— 沉淀成跨平台模板:桌面三系统 + Android/鸿蒙 + iOS 的工程结构、权限声明、打包配置都能直接抄。
它不负责:业务功能、状态管理方案、UI 组件库选型。刻意保持依赖最少,避免第三方库掩盖平台本身的差异。
2. 功能模块
| 环境探测 | 运行时、内核、GPU、权限、能力矩阵 | navigator.userAgentData、navigator.gpu、Permissions API | 浏览器 / Tauri |
| 相机 | 枚举、预览、拍照、两种落盘方式 | getUserMedia、enumerateDevices、Canvas.toBlob | 浏览器 / Tauri |
| 文件能力 | 沙盒 vs 原生,同一操作两套实现 | input[type=file]、File System Access、Rust std::fs | 浏览器 / Tauri |
| GPU 渲染 | WebGPU 与 WebGL2 的最小渲染管线 | requestAdapter、WGSL、WebGL2、copyExternalImageToTexture | 浏览器 / Tauri |
| 网络通信 | 原生 UDP / TCP | Rust std::net、Tauri 事件 | 浏览器 / Tauri |
| Zenoh | client / peer / router 三种模式 + pub/sub | Rust zenoh(原生) | 浏览器 / Tauri |
| 聊天室 | 大厅、房间、在线成员、网络拓扑 | Zenoh presence + pub/sub | 浏览器 / Tauri |
| 日志面板 | 全程记录、过滤、导出 | 内存环形缓冲 + JSON 导出 | 浏览器 / Tauri |
各 Tab 的详细行为:
环境探测:启动即采集,包含运行环境(Tauri 壳 / 纯浏览器)、系统与架构、WebView 内核与版本、UA、安全上下文、跨源隔离、屏幕与 DPR、CPU 核数、设备内存、存储配额、App 私有目录;GPU 部分分别尝试 WebGPU 适配器与 WebGL2 上下文,失败时把原因原样显示;最后渲染一张 26 项的 Web 能力矩阵(WebGPU / WebGL2 / getUserMedia / File System Access / WebCodecs / Web Bluetooth / Web USB / Web Serial / Wake Lock / WebXR 等),并用 Permissions API 预查询相机、麦克风、定位、通知、剪贴板的授权状态。
相机:枚举 videoinput、按设备 ID 启动预览、展示实际生效的分辨率与帧率(track.getSettings)、拍照得到 JPEG Blob;保存提供两条路径——Web 方式触发浏览器下载(落点由浏览器决定),Tauri 方式弹出系统保存对话框后由 Rust 写盘(可写任意路径)。错误按 NotAllowedError / NotFoundError / NotReadableError / OverconstrainedError / SecurityError 分类展示。
文件能力:左列 Web 沙盒(input[type=file] 多选、读前 2KB、showOpenFilePicker、showSaveFilePicker、showDirectoryPicker 列目录并在目录内建文件),右列 Tauri 原生(系统文件/目录对话框、遍历任意目录、任意路径读写文本、App 私有目录写入读回、写二进制 PNG)。两列并排,差异一目了然。
GPU 渲染:优先 WebGPU,失败自动降级 WebGL2;着色器实时渲染动态渐变(证明管线真的在跑),可叠加相机画面或本地图片作为纹理,并输出设备限制参数(maxTextureDimension2D、maxBufferSize、MAX_TEXTURE_SIZE 等)与实时 FPS。
网络通信:Rust 直接开套接字——UDP 单播、255.255.255.255 广播、端口监听(收到的包通过 Tauri 事件推回前端),以及 TCP 连接与发送。WebView 只做 UI,网络 I/O 在 Rust 侧,比「JS → WebSocket 网关 → 转发」少一跳,也才有真正的局域网广播发现能力。
Zenoh:独立页签,两种接入方式并列。
原生 Zenoh(Rust):可选 client / peer / router 三种节点模式,自定义 listen 与 connect 端点,支持发布、订阅与事件回推。
桌面端因此可以自己当局域网里的 router,不需要额外装 zenohd;浏览器通过 headless server 的 HTTP/WS 接口间接使用它。
聊天室:把 Zenoh 的 pub/sub 用成一个可交互的演示。上线后自动广播 presence(昵称、模式、zid、所在房间),
大厅汇总所有在线节点与房间,可创建/加入房间并群聊;左侧拓扑图根据 presence 里的 listen/connect 端点推导节点关系,
router 居中、peer 中环、client 外环,边优先用真实邻居(session.info() 的 zid),其次按端口匹配。
聊天室只走本地 Rust 的原生 Zenoh:桌面应用直接可用,纯浏览器需要 npx 默认启动的 headless 后端。
组网取舍与部署步骤见 docs/Zenoh-局域网通信选型.md 与 docs/Zenoh-跨公网组网指南.md。
日志面板:按级别 / 模块 / 关键字过滤,点开单条看完整 detail,支持复制与导出 JSON。导出内容包含导出时间、UA、运行时元信息与全部条目。
3. 技术栈
| 前端框架 | Vue 3.5(script setup) | 组合式 API,单文件组件 |
| 构建 | Vite 7 | 开发端口固定 1420,与 Tauri devUrl 对齐 |
| 语言 | TypeScript 5.9 | vue-tsc 做类型检查,构建前必过 |
| 样式 | UnoCSS 66(presetWind3) | 原子化 CSS,不引入重型组件库 |
| 壳层 | Tauri 2 | WebView + Rust 原生能力 |
| 图形 | 原生 WebGPU / WebGL2 | 不引入 Three.js,隔离第三方库干扰 |
| 通信 | Tauri IPC(invoke) | 前端 ↔ Rust 自定义命令 |
| 通信协议 | Zenoh 1.10(Rust 原生) | 桌面/headless 直接运行 pub/sub,无需网关 |
| 类型 | @webgpu/types | 补全 lib.dom 缺失的 WebGPU 类型 |
4. 目录结构
WebNativeCapabilityTester/
├── src/ # Vue 前端(浏览器与 Tauri 共用)
│ ├── components/
│ │ ├── EnvInfo.vue # Tab1 环境探测 + 能力矩阵
│ │ ├── CameraTest.vue # Tab2 相机
│ │ ├── FileTest.vue # Tab3 文件能力(Web vs Tauri)
│ │ ├── GpuTest.vue # Tab4 WebGPU / WebGL2
│ │ ├── NetTest.vue # Tab5 网络通信(UDP/TCP)
│ │ ├── NativeChannel.vue # └ Tab5 的 UDP/TCP 面板
│ │ ├── ZenohTest.vue # Tab6 Zenoh(原生 client/peer/router)
│ │ ├── ZenohNative.vue # └ 原生 client/peer/router 面板
│ │ ├── ChatRoom.vue # Tab7 Zenoh 聊天室(大厅/房间/拓扑)
│ │ ├── LogPanel.vue # Tab7 日志面板
│ │ └── StatusPill.vue # 状态徽标
│ ├── composables/
│ │ └── useLog.ts # 全局日志总线(环形缓冲 + 导出)
│ ├── utils/
│ │ └── tauri.ts # Tauri 适配层:isTauri / invokeRust / 对话框 / 保存
│ ├── App.vue # Tab 容器 + 状态栏
│ ├── main.ts
│ ├── style.css
│ └── env.d.ts # FSA API 等缺失类型声明
├── src-tauri/ # Tauri 壳 + Rust
│ ├── src/
│ │ ├── main.rs # 入口(release 隐藏控制台)
│ │ └── lib.rs # 18 个 IPC 命令(文件 / 网络 / Zenoh)+ 插件注册
│ ├── capabilities/
│ │ └── default.json # 权限声明(dialog / fs / clipboard / opener)
│ ├── icons/ # 全平台图标(由 tauri icon 生成)
│ ├── Cargo.toml
│ ├── build.rs
│ └── tauri.conf.json # 窗口、打包、移动端配置
├── deploy/ # 阿里云部署(npx + 本地 Rust)
│ ├── systemd/
│ │ ├── zenohd.service
│ │ └── cap-tester.service
│ └── README.md
├── .github/workflows/ # CI:桌面三平台 + Android 出包
├── docs/
│ ├── 平台构建与安装验证.md # 各平台构建 / 安装 / 验证手册
│ ├── Zenoh-局域网通信选型.md # 手机/PC 局域网与跨公网选型
│ └── Zenoh-跨公网组网指南.md # 手机 + PC + 阿里云的落地步骤(无账号)
├── index.html
├── package.json
├── tsconfig.json
├── uno.config.ts
└── vite.config.ts
5. 环境要求
| Node.js | ≥ 20.19(推荐 22 LTS) | Vite 7 要求 |
| npm | ≥ 10 | 包管理 |
| Rust | ≥ 1.77.2(stable) | Tauri 壳 |
| MSVC Build Tools | 2022(含 C++ 工具链) | Windows 链接器 |
| WebView2 Runtime | 任意新版 | Windows 上的 WebView |
| Xcode Command Line Tools | 最新 | macOS 构建 |
| webkit2gtk-4.1、libsoup-3.0 | 发行版提供 | Linux 构建 |
| JDK 17 + Android SDK/NDK | — | Android 构建 |
| Xcode + Apple 开发者账号 | — | iOS 构建与签名 |
Windows 首次准备:
# Rust(国内建议用镜像,见 FAQ)
winget install Rustlang.Rustup
# MSVC 构建工具(已安装 Visual Studio 可跳过)
winget install Microsoft.VisualStudio.2022.BuildTools --override "--wait --passive --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended"
Linux(Debian/Ubuntu):
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libgtk-3-dev libsoup-3.0-dev
6. 快速开始
npm install
npm run dev
npm run tauri:dev
npm run typecheck
浏览器模式与桌面模式的区别:浏览器模式只能测 Web 标准 API,文件 Tab 的右列会显示「未检测到 Tauri 环境」;桌面模式多出原生对话框、任意路径读写、App 私有目录等能力。同一套代码,两种宿主,这正是对照的价值。
通过 npm 运行网页版(无需克隆仓库)
npx web-native-capability-tester
npx web-native-capability-tester --open
npx web-native-capability-tester --host 0.0.0.0 --port 8080
npx web-native-capability-tester --zenoh ws/192.168.1.10:10000
npx web-native-capability-tester --host 0.0.0.0 --port 8080 --auth admin:密码
部署到阿里云时,npx 默认会把页面与本地 Rust 后端一起拉起,浏览器的 Zenoh / 聊天室直接可用;
只有 --static 纯静态模式才需要另外提供 zenohd 的 WebSocket 网关。
默认就是「网页 + 本地 Rust」
npx web-native-capability-tester 默认会拉起本地 Rust 后端(随平台包安装,无需编译),
文件、UDP/TCP、原生 Zenoh 三种模式开箱可用:
npx web-native-capability-tester
npx web-native-capability-tester --static
此时页面右上角仍是浏览器,但「网络通信」与「Zenoh → 原生 Zenoh」面板会自动变为可用:
前端用 /api/health 探测本地服务,命中后把所有 invokeRust 调用走 HTTP,事件走 /ws。
npm 包是自足的:主包通过 optionalDependencies 自动安装对应平台的后端包,装完即可跑,
不需要本地 Rust 工具链,也不需要联网下载。
web-native-capability-tester | 页面、Node CLI、启动逻辑 |
@wardenfeng/cap-tester-server-win32-x64 等 6 个平台包 | 对应平台的 cap-tester-server 二进制(含 zenoh) |
平台包按 os / cpu 字段匹配,npm 只装当前平台那一个;安装后 CLI 会直接从
node_modules/@wardenfeng/cap-tester-server-<platform> 取二进制。
CLI 的查找顺序:平台包 → CAP_TESTER_SERVER → 包目录 → src-tauri/target/release → PATH。
都没找到时才回退到从 GitHub Release 下载(--no-download 可禁用)。
发布流程(维护者):
node scripts/publish-npm.mjs --dry-run
node scripts/publish-npm.mjs
CI 上由 release-npm.yml 用 NPM_TOKEN 自动完成。
本地开发时等价命令是 npm run web。npm 包只包含网页版(浏览器能测的能力),
文件系统原生访问、原生 Zenoh(client/peer/router)等能力仍需要安装桌面安装包。
Android / iOS 调试(需要对应 SDK):
npm run tauri android dev
npm run tauri ios dev
7. 打包与分发
7.1 本地构建
npm run tauri:build
| Windows | NSIS 安装程序 | nsis/*-setup.exe | ✅ |
| Windows | MSI | msi/*.msi | ✅ |
| macOS | DMG | dmg/*.dmg | 需 macOS |
| Linux | DEB | deb/*.deb | 需 Linux |
| Linux | AppImage | appimage/*.AppImage | 需 Linux |
| Android | APK | gen/android/app/build/outputs/apk/**/*.apk | 需 Android SDK |
| iOS | IPA | 通过 Xcode 归档 | 需 macOS + 证书 |
只构建某一种:
npm run tauri:build -- --bundles nsis
npm run tauri:build -- --bundles msi
npm run tauri:build -- --no-bundle
7.2 CI 出包(推荐)
本机只能产出当前平台的安装包(dmg 需要 Xcode、deb/AppImage 需要 Linux),跨平台产物由 GitHub Actions 完成:
.github/workflows/build-desktop.yml | 推送 v* tag 或手动 | Windows NSIS/MSI、macOS DMG(arm64 + x64)、Linux DEB/AppImage |
.github/workflows/build-android.yml | 推送 v* tag 或手动 | Android APK |
手动触发后,产物同时上传为 workflow artifact;打 tag 时还会创建 draft Release 并附上安装包。
iOS 需要 Apple 开发者证书与 Provisioning Profile,仓库不内置,需在本地 Xcode 或自建 CI 上配置签名后再出 IPA。
8. 安装与运行验证
各平台的安装步骤、常见拦截(Windows SmartScreen、macOS Gatekeeper)、权限放行、以及逐项验证清单,见:
docs/平台构建与安装验证.md
局域网多设备(手机 ↔ PC)用 Zenoh 做通信的适用性、拓扑与排错,见
docs/Zenoh-局域网通信选型.md
验证的核心是同一张表:在每个平台上装好后,依次确认
- 应用能启动,标题栏显示「网页本地能力测试仪」;
- 环境探测页右上角显示「● Tauri 壳」,系统信息与实际一致;
- 相机能弹权限、能出画面、能拍照;
- 文件页右列能调起系统对话框、能写入任意路径;
- GPU 页显示 WebGPU 或 WebGL2 且 FPS 在动;
- 日志页能导出 JSON。
9. 原生 IPC 命令
Rust 侧只暴露 5 个命令,全部在 src-tauri/src/lib.rs 中定义:
get_env_snapshot | — | EnvSnapshot(os/arch/family/exeDir/appDataDir/appConfigDir/cwd/tempDir) | Tab1 展示原生环境与私有目录 |
read_dir_meta | path、limit? | DirEntryMeta[](name/path/isDir/size/modified) | Tab3 遍历任意目录 |
read_text_file | path、maxBytes? | string | Tab3 读取任意路径文本 |
write_text_file | path、contents | 写入字节数 | Tab3 写入任意路径文本、导出日志 |
write_binary_file | path、bytes | 写入字节数 | 拍照/PNG 直接落盘 |
udp_send | host、port、payload | 字节数 | 原生 UDP 单播 |
udp_broadcast | port、payload | — | 原生 UDP 广播(局域网发现) |
udp_listen_start | port | 实际监听地址 | 监听端口,收到包后 emit net://udp |
udp_listen_stop | — | — | 停止监听 |
tcp_connect | host、port | 对端地址 | TCP 连接(3s 超时) |
tcp_send | payload | 字节数 | 在当前连接上发送 |
tcp_close | — | — | 关闭连接 |
zenoh_open | mode、listen[]、connect[] | ZenohStatus(connected/mode/zid) | 打开原生 Zenoh 会话 |
zenoh_close | — | — | 关闭会话 |
zenoh_status | — | ZenohStatus | 查询当前会话状态 |
zenoh_put | key、payload | 字节数 | 发布消息 |
zenoh_subscribe | key | — | 订阅,收到样本后 emit zenoh://sample |
zenoh_unsubscribe | — | — | 退订 |
前端统一通过 src/utils/tauri.ts 的 invokeRust() 调用,返回 { ok, value } 或 { ok, error },调用方无需 try/catch,失败原因直接进 UI 与日志。
10. 权限配置
Tauri v2 的权限在 src-tauri/capabilities/default.json 中声明:
{
"identifier": "default",
"windows": ["main"],
"permissions": [
"core:default",
"dialog:default",
"opener:default",
"clipboard-manager:default",
"fs:default",
"fs:allow-read-text-file",
"fs:allow-write-text-file",
"fs:allow-read-dir",
"fs:allow-write-file",
{ "identifier": "fs:scope", "allow": [{ "path": "**" }, { "path": "**/*" }] }
]
}
说明:
dialog 提供系统文件/目录选择与保存对话框;
clipboard-manager 提供剪贴板读写(复制环境报告);
fs:scope 放开到 ** 仅用于演示;生产环境应缩小到固定目录;
- 真正的任意路径读写走的是 Rust 自定义命令,不受 fs 插件 scope 限制,这是本项目刻意保留的对照点。
相机权限不在 Rust 侧声明:getUserMedia 是 WebView 标准 API,Android 由 Tauri 自动注入 CAMERA 权限,iOS 需要在 Info.plist 中提供 NSCameraUsageDescription(Tauri 移动端配置里填写)。
11. 平台能力边界速查
实测/文档对照表,用于快速判断某平台大概能做什么:
| WebGPU | ✅ WebView2 新版稳定 | ⚠️ Safari 18+/WKWebView 视系统版本 | ⚠️ 取决于 WebKitGTK 版本 | ⚠️ 视 WebView 版本,部分鸿蒙机型缺失 | ⚠️ iOS 17+ 较稳,低版本降级 WebGL |
| WebGL2 | ✅ | ✅ | ✅ | ✅ | ✅ |
| getUserMedia | ✅ 系统权限 | ✅ 系统权限 | ✅ 需 PulseAudio | ✅ 运行时权限,切后台释放流 | ✅ 权限管控严格 |
| 遍历任意目录 | ✅ Rust | ✅ Rust | ✅ Rust | ⚠️ 受分区存储限制 | ❌ 仅 App 沙盒 |
| 写任意路径 | ✅ Rust | ✅ Rust | ✅ Rust | ⚠️ 受分区存储限制 | ❌ 仅沙盒内 |
| File System Access API | ✅ Chromium 内核 | ⚠️ 仅 Chromium 系浏览器 | ✅ Chromium 系 | ⚠️ 支持度参差 | ❌ Safari 不支持 |
| 系统对话框 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 后台保活 | ✅ | ✅ | ✅ | ⚠️ 需前台服务 + 通知 | ⚠️ 系统限制严格 |
表格是测试起点,不是结论。真实结果以各平台实测日志为准,导出 JSON 后回填。
12. 设计原则
- 每个模块独立 —— 一个 Tab 只测一个能力,出错时能立刻分清是平台问题还是代码问题。
- 失败状态必须可见 —— 权限拒绝、不支持、异常全部渲染到 UI,不静默吞掉,也不降级成「看起来成功了」。
- 日志即文档 —— 所有操作带时间戳、模块、级别、detail;导出 JSON 就是一份可交付的边界报告。
- 不引入重型库 —— 先确认原生 API 本身能跑,再考虑上层封装;GPU 部分刻意手写 shader。
- 同一份前端跑两种宿主 —— 能力差异只通过
isTauri() 分支体现,避免维护两套代码。
13. 常见问题
Q:环境探测里 WebGPU 显示不可用?
先看失败原因文案:navigator.gpu 不存在说明内核没暴露 WebGPU(WebView2 旧版本、部分 Linux 发行版、旧鸿蒙);requestAdapter() 返回 null 通常是显卡被禁用、驱动过旧或运行在虚拟机里。WebGL2 通常仍可用,GPU Tab 会自动降级。
Q:相机是黑屏 / 报 NotAllowedError?
桌面端先确认系统设置里允许应用访问摄像头(Windows:设置 → 隐私和安全性 → 相机);移动端确认已授予运行时权限;浏览器里必须是 https 或 localhost(isSecureContext 为 true)。
Q:文件页右列一直提示「未检测到 Tauri 环境」?
说明前端跑在普通浏览器里。用 npm run tauri:dev 打开桌面壳;或检查 window.__TAURI_INTERNALS__ 是否存在。
Q:Windows 上 npm run tauri:build 卡在下载 NSIS/WiX?
Tauri 首次打包会下载打包工具,网络受限时会卡住。可以先 --no-bundle 验证 exe,或配置代理后再打包;CI 上不存在这个问题。
Q:Rust 安装很慢或超时?
官方源在国内常超时,改用镜像:
$env:RUSTUP_DIST_SERVER='https://mirrors.tuna.tsinghua.edu.cn/rustup'
$env:RUSTUP_UPDATE_ROOT='https://mirrors.tuna.tsinghua.edu.cn/rustup/rustup'
# 下载 rustup-init.exe 后执行
rustup-init.exe -y --default-toolchain stable --default-host x86_64-pc-windows-msvc
crates 依赖同理,可在 ~/.cargo/config.toml 中把 crates.io 换成镜像源。
Q:安装包被 SmartScreen / Gatekeeper 拦截?
未签名安装包的正常现象。Windows 点「更多信息 → 仍要运行」;macOS 在「隐私与安全性」里点「仍要打开」,或执行 xattr -dr com.apple.quarantine /Applications/xxx.app。
Q:想同时开多个实例调试?
桌面端默认支持多开(Windows 上直接双击两次即可)。多开时有两点注意:
- 每个实例的「进程 ID」显示在环境探测页,用来区分窗口;
- 若两个实例都以
peer 模式监听同一个 7447,第二个会因端口占用失败,改成不同端口或 client 模式即可。
Q:Zenoh 连接失败?
本项目的 Zenoh 面板使用 Rust 原生实现,不需要 zenohd。若页面提示不可用,说明当前是纯静态模式:用 npx web-native-capability-tester 默认启动(会拉起本地后端),或安装桌面应用。
Q:Linux 上白屏?
多半是 webkit2gtk 版本与 Tauri 2 要求不匹配(需要 4.1),或缺少图形驱动;用 WEBKIT_DISABLE_COMPOSITING_MODE=1 启动可排除合成器问题。
14. 扩展一个新的能力测试
- 在
src/components/ 下新建组件,命名 XxxTest.vue;
- 在
src/App.vue 的 tabs 数组里加一项;
- 组件内统一用
log.info/success/warn/error 打点,错误文案要带原始错误名(如 NotAllowedError);
- 若需要原生能力,在
src-tauri/src/lib.rs 加命令并在 capabilities/default.json 放权限,前端通过 invokeRust() 调用;
- 在
docs/平台构建与安装验证.md 的验证清单里补一行。
判断标准:新模块在没有 Tauri 的浏览器里也要能优雅降级(显示「不可用 + 原因」),而不是白屏或报错。
15. 许可证
MIT
16. 分支与发布
main 是受保护分支:禁止直接 push,所有改动必须通过 PR 合并(rebase,保持线性历史);
- 版本号在三处保持一致:
package.json、src-tauri/tauri.conf.json、src-tauri/Cargo.toml;
- 合并到
main 后,auto-release 会检查版本对应的 tag:
- 不存在 → 打 tag
v<version>,构建 Windows / macOS / Linux 安装包与 Android APK,并发布 npm 平台包与主包;
- 已存在 → 跳过,不重复发布;
- 想发新版:改三处版本号,开 PR 合并即可,无需手动打 tag。