跳到内容

适配 OpenHarmony ArkWeb 的 DOM storage:从白屏到上游 PR 的完整历程

更新于
约 8 分钟阅读
·
1370 字
踩坑记录
#Tauri#OpenHarmony#HarmonyOS#ArkWeb#wry#localStorage#ohpm#HAR#开源贡献
🛈
本文由 AI 辅助生成

本文部分或全部内容由 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 恒为 nullsessionStorage 同理);
  • 前端任何 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/abilitygithub.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 契约 —— WebviewCreateRequestcrates/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_accessdomStorageAccess,跟 initialization_scriptsinitializationScripts 同理,所以两端命名按各自习惯写就行。

③ 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) 显式关闭。

收获

回头看,这是一条很典型的「发现平台差异 → 本地打补丁过渡 → 上游可配置化根治」的路子,几个点值得记下:

  1. 「平台默认行为差异」要优先怀疑,而不是前端 bug。 同一份前端 Android/iOS 正常、OHOS 挂,八成是 webview 平台的默认值不同。ArkWeb 的 DOM storage 默认关、Chromium 内核不等于行为完全一致,都是这类坑。
  2. 本地硬编码补丁是过渡,不是终点。 先硬开 .domStorageAccess(true) 让包跑通没问题,但要把「为什么这么做」沉淀成文档,并把「上游可配置化」作为长期方向,而不是让 patch 永远躺在本地。
  3. ohpm 的 file: 依赖别指向源码目录。 相对导入会越界报 00309001;重新打包成 .har(gzip tar、顶层 package/)才是稳定做法。
  4. 提 PR 前重核对上游,别信本地副本。 上游从单包重构成插件化、通信协议从序列化改成了具名 N-API 契约,我手里的 vendored 副本完全过时了。以 git pull 后的最新 main 为准。
  5. 贡献的本质是「串链路」+「定默认值」。 代码量不大,难的是把新字段正确穿过 Rust builder → napi → ArkTS 组件,以及想清楚默认该开还是该关(对齐 Android/iOS = 默认开)。

如果你也在给 OHOS 适配 webview 应用、撞上 localStoragenull 的白屏,先检查 webview 组件有没有 .domStorageAccess(true);等这份上游 PR 合进去,就再也不用自己 patch 了。

CC BY-NC-SA 4.0

非商业转载请注明出处,商业转载请联系作者获得授权。

For non-commercial use, please indicate the source. For commercial use, please contact the author for authorization.

View license

评论