
适配 OpenHarmony ArkWeb 的 DOM storage:从白屏到上游 PR 的完整历程
本文部分或全部内容由 AI 辅助撰写。AI 生成的内容可能存在不准确之处,请读者注意甄别。
给 Tauri v2 移动端(agent-mobile)适配 HarmonyOS / OpenHarmony 的时候,webview 是 ArkWeb(Chromium 内核)。折腾了一圈,遇到一个「平台默认行为差异」的经典问题:ArkWeb 默认关闭 DOM storage,导致 localStorage 直接是 null。这篇把从发现问题、本地临时改包,到最后向上游提 PR 的完整思路与历程记下来。
现象:启动白屏 + 「Cannot read properties of null」
OHOS 包第一次跑起来,直接白屏,日志里是一条很常见的 JS 报错:
TypeError: Cannot read properties of null (reading 'getItem')
顺着往下查,是一串连锁反应:
window.localStorage恒为null(sessionStorage同理);- 前端任何
localStorage.getItem/setItem都会抛空指针; - token / gateway 地址 / 设置无法持久化,于是又出现「Gateway token is required」、历史记录缺失、没有模型配置。
而同样一份前端,Android / iOS 上完全正常。第一反应就该想到:这不是前端 bug,是 webview 平台的默认行为差异。
定位:ArkWeb 默认关闭 DOM storage
ArkWeb 虽然内核是 Chromium,但 DOM storage 这一项是默认关闭的,必须显式在 Web() 组件上调用:
Web({ src: "...", controller })
.javaScriptAccess(true)
.domStorageAccess(true) // ← 不写这句,localStorage 就是 null
那 Tauri 的 OHOS 适配为什么没写这句?看调用链就明白了:
wry (ohos/mod.rs)
→ openharmony_ability::WebViewBuilder(Rust)
→ WebViewInitData(napi 序列化)
→ @ohos-rs/ability(ArkTS ohpm 包)的 DefaultWebview.ets 构建 Web() 组件
问题就出在最后一环:上游 @ohos-rs/ability(github.com/harmony-contrib/openharmony-ability)构建 Web() 组件时,从头到尾没有调用过 .domStorageAccess()。于是所有走这条链路的 OHOS 应用,localStorage 都是 null。
过渡方案:本地硬开 + 重新打包 .har
「先让包跑通」是第一优先级。最直接的改法,是在本地 patch DefaultWebview.ets,硬加一行:
.javaScriptAccess(data?.javascriptEnable)
.domStorageAccess(true) // ← 本地补丁:硬开启 DOM storage
.mediaPlayGestureAccess(...)
但这里踩了个 ohpm 的坑。ohpm 的 file: 依赖有两种形态:
| 形态 | ohpm 行为 | ArkTS 编译结果 |
|---|---|---|
file:../dir(源码目录) |
符号链接到项目外源码 | ❌ 报 00309001 Cannot import files outside of the current module using relative paths |
file:../xxx.har(har 文件) |
解包到 gen/ohos/oh_modules/.ohpm/ 内 |
✅ 等同 registry 安装,作为独立 HAR module 正常编译 |
也就是说,不能直接 file: 指向改好的源码目录,必须把它重新打包成 .har(.har 就是 gzip 打包的 tar,顶层 package/):
cp -r @ohos-rs/ability /tmp/har-build/package
cd /tmp/har-build && tar czf @ohos-rs-ability-0.4.0-beta.0-patched.har package/
然后在 oh-package.json5 里指向这个 .har:
"@ohos-rs/ability": "file:../../../ohos-packages/@ohos-rs-ability-0.4.0-beta.0-patched.har"
本地补丁的完整说明我都沉淀在 ohos-packages/README.md 里了,包括如何验证产物里确实出现了 domStorageAccess:
grep -c domStorageAccess <path>/entry-default-unsigned.hap # 期望输出 1
这一步让 OHOS 端彻底跑通,localStorage 正常、token 能持久化了。但「本地硬开」终究是过渡方案——它治标,不治本:上游不发版,任何下游想用都得自己重复这份 patch。
长期方向:向上游提可配置化 PR
真正的根治,是让上游 openharmony-ability 支持 domStorageAccess,并且做成可配置开关,而不是把 true 硬编码进去。于是我去 fork 了一份,准备提 PR。
结果一拉代码发现——上游早就重构了。我手里那份 vendored 副本还是 0.4.0-beta.0 的老结构(webview 在 crates/ability/src/webview/ + native_ability/.../DefaultWebview.ets),但上游 main 已经是 1.0.0-beta.x,webview 被拆成了一个独立插件:
crates/plugin-webview/ # Rust facade(openharmony-ability-plugin-webview)
└─ src/lib.rs # WebviewCreateRequest 等 #[napi(object)] 契约
plugins/webview/ # ArkTS HAR(@ohos-rs/ability-plugin-webview)
└─ src/main/ets/WebviewPlugin.ets
而且通信协议也变了:从原来的直接序列化,改成了具名 N-API 契约(impl_bridge_napi_type! + 两端按 typeName 校验),明确禁止 JSON。第一课:提 PR 前一定要先重新核对上游最新结构,别拿本地过期的 vendored 副本当基准。
把 domStorageAccess 串过整条链路
在新架构下,domStorageAccess 要像 javascriptEnabled 一样,从 Rust 一路传到 ArkTS 的 Web() 组件,一共四层:
① Rust 契约 —— WebviewCreateRequest(crates/plugin-webview/src/lib.rs)
加字段、初始化、builder 方法:
pub struct WebviewCreateRequest {
pub javascript_enabled: Option<bool>,
/// ArkWeb 默认关闭 DOM storage,ArkTS 侧默认开启以对齐 Android/iOS。
pub dom_storage_access: Option<bool>,
// ...
}
pub fn dom_storage_access(mut self, enabled: bool) -> Self {
self.dom_storage_access = Some(enabled);
self
}
② 具名 N-API 类型
WebviewCreateRequest 通过 impl_bridge_napi_type!(..., "ohos.webview.CreateRequest") 固定了 typeName。napi 会自动把 Rust 的 snake_case 字段转成 ArkTS 的 camelCase——dom_storage_access → domStorageAccess,跟 initialization_scripts → initializationScripts 同理,所以两端命名按各自习惯写就行。
③ ArkTS payload —— WebviewCreatePayload
interface WebviewCreatePayload {
javascriptEnabled?: boolean | null;
domStorageAccess?: boolean | null;
// ...
}
④ 真正生效 —— BuildWebview 里的 Web() 组件
.javaScriptAccess(data.javascriptEnabled ?? true)
.domStorageAccess(data.domStorageAccess ?? true) // ← 可配置,默认开启
.mediaPlayGestureAccess(data.autoplay === true ? false : true)
关键决策是默认值:我选默认 true(?? true),理由跟 javascriptEnabled 一致——这是跨平台适配层,默认行为应对齐 Android/iOS WebView,让 localStorage 开箱即用;需要的人再 .dom_storage_access(false) 显式关闭。
收获
回头看,这是一条很典型的「发现平台差异 → 本地打补丁过渡 → 上游可配置化根治」的路子,几个点值得记下:
- 「平台默认行为差异」要优先怀疑,而不是前端 bug。 同一份前端 Android/iOS 正常、OHOS 挂,八成是 webview 平台的默认值不同。ArkWeb 的 DOM storage 默认关、Chromium 内核不等于行为完全一致,都是这类坑。
- 本地硬编码补丁是过渡,不是终点。 先硬开
.domStorageAccess(true)让包跑通没问题,但要把「为什么这么做」沉淀成文档,并把「上游可配置化」作为长期方向,而不是让 patch 永远躺在本地。 - ohpm 的
file:依赖别指向源码目录。 相对导入会越界报00309001;重新打包成.har(gzip tar、顶层package/)才是稳定做法。 - 提 PR 前重核对上游,别信本地副本。 上游从单包重构成插件化、通信协议从序列化改成了具名 N-API 契约,我手里的 vendored 副本完全过时了。以
git pull后的最新main为准。 - 贡献的本质是「串链路」+「定默认值」。 代码量不大,难的是把新字段正确穿过 Rust builder → napi → ArkTS 组件,以及想清楚默认该开还是该关(对齐 Android/iOS = 默认开)。
如果你也在给 OHOS 适配 webview 应用、撞上 localStorage 是 null 的白屏,先检查 webview 组件有没有 .domStorageAccess(true);等这份上游 PR 合进去,就再也不用自己 patch 了。
非商业转载请注明出处,商业转载请联系作者获得授权。
For non-commercial use, please indicate the source. For commercial use, please contact the author for authorization.
View license