ArkWeb + HarmonyOS H5通信:桥接白名单与注入时序【共创稿事节】
一个 H5 页面嵌进 HarmonyOS 应用后,页面上可以正常点击、滚动,却在调用 ArkTS 方法时报“对象不存在”,有时刷新一次又好了。这类问题通常不是 JavaScript 语法错了,而是 Web 组件初始化、桥接对象注册和页面执行脚本的顺序没有对齐。另一个经常被忽略的风险是:桥接一旦暴露了过多原生方法,加载页面的范围发生变化时,调用入口也会随之扩大。
本篇用一个“本地帮助中心”作为独立场景:帮助页只允许向 ArkTS 请求应用版本说明和当前主题颜色,不涉及定位、通讯录或文件系统权限。我们从可解释的最小接口出发,分别解决什么时候注册、允许谁调用和网页参数如何校验三个问题。核心思路不是给 Web 页面一个万能对象,而是建立受控、易审计的接口边界。

第一处断点:先分清 Web 与 Controller 的注册入口
HarmonyOS ArkWeb 为前端页面调用应用侧函数提供两条典型路径:在组件初始化时通过 javaScriptProxy() 配置,或者在初始化后通过 WebviewController.registerJavaScriptProxy() 注册。二者的用途相近,但挂载位置和时序不同。前者绑定在 Web 组件声明中,适合接口固定的页面;后者用于需要在运行过程中管理桥接对象的情况,动态注册后应按官方要求刷新页面使配置生效,并在合适的时机通过 deleteJavaScriptRegister() 解除注册。
选择固定的帮助中心页面时,优先在 Web 构建声明里配置桥接。这样页面首次加载前就有明确的对象定义,也避免把“注册成功”寄托于页面已经发出的某条事件。要注意桥接对象不是 HTTP 接口,网页代码可能在加载早期执行,所以只在页面加载完成以后才临时注册,容易形成初次失效、刷新有效的错觉。
最小桥接类不需要拥有整个应用上下文,只暴露两个返回值受限的方法。为提高代码可检查性,字符串长度、参数类型与允许的枚举都由应用侧负责,而不是相信 H5 页面自行遵守约定。
// entry/src/main/ets/bridge/HelpBridge.ets
export class HelpBridge {
getAppEdition(): string {
return '1.0.1';
}
requestTheme(theme: string): string {
if (theme !== 'light' && theme !== 'dark') {
return 'INVALID_THEME';
}
return `ACCEPTED:${theme}`;
}
}
示例中的应用版本是帮助中心的展示数据,真实项目应对接工程里的可信版本来源,不应让网页发来的任意字符串反向覆盖应用配置。requestTheme() 只作响应校验演示,没有修改系统主题;如果需要改变 ArkUI 状态,应在受控业务层发出明确状态更新,避免桥接对象直接操纵全局单例。
第二处断点:把允许调用的对象和方法说清楚
下列 Stage 页面使用本地 rawfile HTML 作为页面源,并通过 javaScriptProxy 绑定桥接对象。methodList 精确限定暴露的方法,不要将对象上的所有成员放入列表。当前官方指南还提供了 permission 参数用于约束页面 URL,实际开发可按目标 SDK 校验其支持版本及行为,避免在远程页面场景里默认打开所有域名。
// entry/src/main/ets/pages/HelpCenter.ets
import { webview } from '@kit.ArkWeb';
import { HelpBridge } from '../bridge/HelpBridge';
@Entry
@Component
struct HelpCenter {
private controller: webview.WebviewController =
new webview.WebviewController();
private bridge: HelpBridge = new HelpBridge();
build() {
Column() {
Web({ src: $rawfile('help.html'), controller: this.controller })
.javaScriptAccess(true)
.javaScriptProxy({
object: this.bridge,
name: 'helpNative',
methodList: ['getAppEdition', 'requestTheme'],
controller: this.controller,
permission: JSON.stringify({
javascriptProxyPermission: {
urlPermissionList: [
{ scheme: 'resource', host: 'rawfile', port: '', path: '' }
]
}
})
})
.width('100%')
.height('100%');
}
}
}
与这种写法配套的 help.html 放在模块的 resources/rawfile/ 目录下;它从页面侧通过 window.helpNative 调用已注册的方法。上面 resource/rawfile 的权限配置是依据官方 JavaScriptProxy 文档中的本地资源 URL 格式编写的示例。真实工程应按照当前 SDK 对目标地址及权限字段的解释进行核对,特别是从本地资源变成远程 HTTPS 页面时,不允许直接复用面向所有资源的规则。

<!-- entry/src/main/resources/rawfile/help.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head>
<body>
<button id="edition">查询版本</button>
<button id="theme">切换深色</button>
<p id="result" aria-live="polite">帮助中心已就绪</p>
<script>
const result = document.getElementById('result');
const show = value => { result.textContent = String(value); };
document.getElementById('edition').addEventListener('click', () => {
if (!window.helpNative) return show('桥接尚未就绪');
show(window.helpNative.getAppEdition());
});
document.getElementById('theme').addEventListener('click', () => {
if (!window.helpNative) return show('桥接尚未就绪');
show(window.helpNative.requestTheme('dark'));
});
</script>
</body>
</html>
HTML 里的空值判断可以改善用户提示,却不是安全措施。核心授权必须在原生侧完成:对象只注册必要方法,页面地址按规则限制,方法参数在 ArkTS 层再次检查。如果某个业务需要开放系统能力,还要分别核对对应 Kit 的权限声明和授权流程。不要因为网页按钮被隐藏,就认定用户无法触发底层方法。
第三处断点:为什么“首次无效,刷新有效”
排查时应先画出时间线:创建 WebviewController、声明 Web 组件、注册桥接、页面解析脚本、用户点击调用。若把 registerJavaScriptProxy() 放在网页完成加载之后,却没有让页面重新建立 JavaScript 上下文,页面很可能仍看不到新方法。官方指南明确提到动态注册后需要 refresh() 使配置生效,因此不要仅凭控制台打印了“register success”就判定前端已经可以调用。
另外,onPageEnd 也不能被简单当成“此后所有脚本永远有效”的保证。页面重定向、重新加载以及销毁重建组件,都可能重新改变 JavaScript 上下文。最稳妥的做法是在业务设计上明确哪些方法属于全生命周期接口,哪些对象只在某个局部页面有效;固定页面尽量使用初始化声明,动态对象则成对管理注册与删除。
排查时可以在浏览器页面脚本中分别输出 typeof window.helpNative 与 typeof window.helpNative?.getAppEdition,在原生侧通过已支持的日志工具记录控制器是否被重新创建;再让页面分别在首次加载和点击按钮时调用。若第一次 undefined 而刷新后正常,优先检查注册时序;若桥接存在但方法缺失,优先检查 methodList;若方法存在却没有返回预期值,检查参数类型、返回值和业务执行分支。

第四处断点:页面来源与权限白名单
“只载入公司页面”不是一个自动成立的安全结论。Web 资源可能重定向,线上域名也可能部署了不可信内容。一个桥接方法若能访问设备文件、账号身份或应用权限数据,意味着页面的脚本拥有了进入原生能力的通道。因此,在决定开放某个方法前,应先问:这个功能需要哪一类信息,是否能用更窄的只读接口代替,是否必须支持动态网页?
permission 的重要性在于将调用能力与页面 URL 规则建立绑定,而不仅是通过 methodList 过滤函数名。权限限制同时支持对象级与方法级入口,官方示例采用 scheme、host、port 和 path 描述允许 URL 的范围。实际业务中最好以单独的允许域名/路径配置管理,并针对重定向、嵌入页面和不受控第三方脚本做检查;不要构造一个空白通配规则,试图一次性兼容所有站点。
参数校验是第二道防线。像本例的 requestTheme() 应严格接受已知枚举。如果未来增加“打开某个应用页面”的函数,不能直接把网页传来的任意字符串拼进内部路由;应将允许跳转的目标列成映射表。若未来需要 H5 把大块 JSON 传给 ArkTS,必须约束最大长度、字段类型和可用操作,避免无界解析或未授权请求。
原生回调返回到前端时还要考虑 HTML 注入。页面示例使用 textContent 展示结果,而不是把后端或原生返回的任意字符串直接赋给 innerHTML。这种细节不依赖某个平台专有 API,却直接影响桥接的安全结果。
把故障排查转成可回归的检查
建议针对帮助中心做几组简单场景:首次打开本地页面时查询版本;重新加载后再次查询;传入未允许的主题词应返回 INVALID_THEME;将页面源改为未经授权的地址时,应检查桥接权限是否阻止调用;销毁并重建页面以后,再观察是否出现重复注册或失效对象。所有步骤都有明确输入和期望行为,适合在项目的模拟器或真机环境逐项验证。这里没有给出未经执行的耗时、崩溃率或测试通过率。
安全问题的诊断顺序也应该和普通的“页面加载失败”不同。遇到对象缺失,先看生命周期;遇到方法缺失,看白名单;遇到异常返回,看参数和类型;遇到不该有的权限,立刻回到页面来源和桥接配置,而不是为方便开发移除验证。一个能正常调用的桥接,不等于一个可安全交付的桥接。
最值得保留的工程习惯,是让每个原生桥接方法都能回答三个问题:哪个页面可以调用、允许输入什么、原生侧究竟能做什么。只有这三条边界都清楚,H5 与 HarmonyOS 的混合开发才不会因为一个小功能需求,把整个应用的权限通道打开。后续根据项目需要扩展能力,也应继续遵循同样的最小暴露原则。
更多推荐

所有评论(0)