智能客服
你问我答,随时在线为你解决问题
浏览器的扩展程序(extension)支持与系统上安装的应用交换消息,应用向扩展提供服务,帮助扩展实现一些应用才具备的能力,常见的例子是密码管理器:应用负责存储和加密你的密码信息,以便浏览器扩展程序自动填充网页中的表单字段。
从API version 21开始,支持开发者在应用中使用WebNativeMessagingExtensionAbility组件,为浏览器扩展提供后台服务能力。
浏览器扩展通过WebExtensions runtime API连接WebNativeMessagingExtensionAbility,双方通信是通过共享pipe文件描述符后调用IO接口实现。

本文将浏览器扩展调用WebExtension接口runtime.connectNative建立的连接称为NativeMessaging连接。
NativeMessaging面向两类开发者:应用开发者和浏览器应用开发者。两者均需要了解WebNativeMessagingExtensionAbility运作机制,但关注的场景和接口不同。应用开发者关注WebNativeMessagingExtensionAbility组件的使用,负责相关业务开发;浏览器应用开发者负责建立NativeMessaging连接,关注WebNativeMessagingExtensionManager相关接口。
本文会在具体的描述中,特意标注需要哪类开发者关注。
对于API版本21-23,WebNativeMessagingExtensionAbility组件仅支持2in1设备;从API版本24开始,增加支持在平板上使用。
WebNativeMessagingExtensionAbility组件无需额外权限,允许任意三方应用集成使用,但拉起方(浏览器)需申请ACL权限(ohos.permission.WEB_NATIVE_MESSAGING)。此权限仅对浏览器类应用开放。
WebNativeMessagingExtensionAbility组件内不支持调用Window相关API。
WebNativeMessagingExtensionAbility仅支持拉起本应用的UIAbility,不支持拉起其他应用UIAbility或者其他类型ExtensionAbility。
WebNativeMessagingExtensionAbility仅用于浏览器扩展与应用通信场景,不支持如后台服务等其他场景使用。
应用包名仅允许使用小写英文字母、数字、下划线(_)、点(.)。

WebNativeMessagingExtensionAbility为单实例独立进程,多次调用connectNative接口仅拉起一个实例,同时触发多次onConnectNative回调,需要应用管理多会话场景。
应用集成WebNativeMessagingExtensionAbility时,需要通过dataShare能力向浏览器应用提供extension配置。该配置用于浏览器应用判断允许访问的扩展及指定要拉起的WebNativeMessagingExtensionAbility名称。
extension配置采用json字符串格式
extension配置格式:
- {
- // 应用包名
- "name": "com.example.myapplication",
- // 具体描述
- "description": "Send message to native app.",
- /*
- * WebNativeMessagingExtensionAbility名称,用于元能力want填充abilityName,一个应用应只有一个
- * WebNativeMessagingExtensionAbility
- */
- "abilityName": "webExtensionAbility",
- /*
- * 允许访问该WebNativeMessagingExtensionAbility的浏览器扩展url信息,不同的浏览器的扩展有不同的scheme协议,华为浏览器使用chrome-extension协议头
- */
- "allowed_origins":[
- "chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/"
- ]
- }
extension配置通过dataShare配置项向浏览器应用暴露,具体的配置方式可参考下方实现一个WebNativeMessagingExtensionAbility(应用开发者)中步骤6。其中,uri为固定格式:datashareproxy://[包名]/browserNativeMessagingHosts,value字段填写上述extension配置的JSON字符串,allowList字段填写允许访问该配置的浏览器应用的appIdentifier。
NativeMessaging连接使用的具体格式,每个消息都使用JSON进行序列化,编码为UTF-8,并在前面附加32位消息长度(采用原生字节顺序)。来自WebNativeMessagingExtensionAbility的单个消息的大小上限为 1 MB,这主要是为了保护浏览器免受行为异常的应用影响。发送到WebNativeMessagingExtensionAbility的消息大小上限为 64 MB。
需按w3c标准配置manifest.json和background.js实现通信。
支持使用chrome.runtime.connectNative或chrome.runtime.sendNativeMessage进行连接。
配置插件内容,发送ping字符串并接收pong响应的插件代码,示例如下:
实现配置manifest.json
- {
- "name": "com.example.myapplication",
- "version": "1.0.1",
- "description": "Launch APP",
- "manifest_version": 3,
- "permissions": ["nativeMessaging", "tabs", "scripting"], // 根据实际场景是否需要进行选择
- "host_permissions": ["http://*/*", "https://*/*", "ftp://*/*", "file://*/*"], // 根据实际场景选择
- "background": {
- "service_worker": "background.js" // 用于运行插件runtime命令
- },
- "content_scripts": [
- {
- "matches": ["http://*/*", "https://*/*", "ftp://*/*", "file://*/*"], // 根据实际场景选择
- "js": ["main.js"] // 用于运行插件js命令
- }
- ],
- "action": {
- "default_popup": "index.html" // 插件页面展示
- }
- }
实现main.js
- // 从html中触发调用
- function sendMessageToNative() {
- var message = "ping"; // 发送ping
- chrome.runtime.sendMessage({
- type: "sendMessage",
- message: message
- }, function (response) {});
- }
实现配置background.js
使用chrome.runtime.connectNative连接
- var port = null;
- // 监听来自main.js的信息
- chrome.runtime.onMessage.addListener(
- function (request, sender, sendResponse) {
- if (request.type == "sendMessage") {
- if (port == null) {
- connectToNativeHost();
- }
- port.postMessage(request.message); // 向应用程序发送信息
- }
- return true; // 保持消息通道开放
- });
- function connectToNativeHost() {
- var bundleName = "com.example.app"; // 插件对应应用的bundleName
- port = chrome.runtime.connectNative(bundleName); // 根据bundleName名得到通信端口port
- port.onMessage.addListener(onNativeMessage); // 监听native应用程序是否发来消息
- port.onDisconnect.addListener(onDisconnected); // 监听是否断开连接
- }
- // 接收到来自native程序的消息时触发
- async function onNativeMessage(message) {
- console.info('接收到从本地应用程序发送来的消息:' + JSON.stringify(message)); // 示例中的pong
- }
- // 断开连接时触发
- function onDisconnected() {
- port = null;
- }
使用chrome.runtime.sendNativeMessage连接
- function sendNativeMessage() {
- var bundleName = "com.example.app"; // 插件对应应用的bundleName
- var nativeMessage = "ping"; // 插件要发给应用的内容
- chrome.runtime.sendNativeMessage(
- bundleName,
- {message: nativeMessage},
- function(response) {
- // 收到一次应用回复的信息后断开连接
- console.info("sendNativeMessage收到应用程序响应:", JSON.stringify(response));
- }
- )
- }
在DevEco Studio工程中手动新建一个WebNativeMessagingExtensionAbility组件,具体步骤如下:
在工程Module对应的ets目录下,右键选择“New > Directory”,新建一个目录并命名为MyWebNativeMessageExtAbility。
在MyWebNativeMessageExtAbility目录,右键选择“New > ArkTS File”,新建一个文件并命名为MyWebNativeMessageExtAbility.ets。
其目录结构如下所示:
- ├── ets
- │ ├── MyWebNativeMessageExtAbility
- │ │ ├── MyWebNativeMessageExtAbility.ets
- └
在MyWebNativeMessageExtAbility.ets文件中,增加导入WebNativeMessagingExtensionAbility的依赖包,自定义类继承WebNativeMessagingExtensionAbility组件并实现生命周期回调。
- import { WebNativeMessagingExtensionAbility, ConnectionInfo } from '@kit.ArkWeb';
- import { hilog } from '@kit.PerformanceAnalysisKit';
- import {buffer, util} from '@kit.ArkTS';
- import { fileIo } from '@kit.CoreFileKit';
-
- const TAG: string = '[MyWebNativeMessageExtAbility]';
- const DOMAIN_NUMBER: number = 0xFF00;
-
- export default class MyWebNativeMessageExtAbility extends WebNativeMessagingExtensionAbility {
- // 读取扩展发来的消息,并回复
- async ReadAsync(fdRead:number, fdWrite:number) : Promise<void> {
- try {
- // read
- let arrayBuffer = new ArrayBuffer(1024);
- let readLen = await fileIo.read(fdRead, arrayBuffer);
- if (readLen <= 4) {
- hilog.error(DOMAIN_NUMBER, TAG, 'read pipe length failed');
- return;
- }
- hilog.info(DOMAIN_NUMBER, TAG, 'read pipe %{public}s', buffer.from(arrayBuffer, 4, readLen - 4).toString());
-
- // write
- let strResponse : string = "pong";
- const encoder = new util.TextEncoder("utf-8");
- const strBytes = encoder.encodeInto(strResponse);
- let bufferLen = strBytes.length;
- const lenBytes = new Uint8Array(4);
- lenBytes[0] = (bufferLen >> 0) & 0xFF;
- lenBytes[1] = (bufferLen >> 8) & 0xFF;
- lenBytes[2] = (bufferLen >> 16) & 0xFF;
- lenBytes[3] = (bufferLen >> 24) & 0xFF;
- const writeBuffer = new Uint8Array(4 + bufferLen);
- writeBuffer.set(lenBytes, 0);
- writeBuffer.set(strBytes, 4);
- let writeLen = await fileIo.write(fdWrite, writeBuffer.buffer);
- hilog.info(DOMAIN_NUMBER, TAG, 'write pipe length %{public}d', writeLen);
- } catch (err) {
- hilog.error(DOMAIN_NUMBER, TAG, 'fileIo failed, error code: ' + err.code + " message: " + err.message);
- }
- }
-
- onConnectNative(info: ConnectionInfo): void {
- hilog.info(DOMAIN_NUMBER, TAG,
- `onConnectNative, connectionId ${info.connectionId} caller bundle: ${info.bundleName}, extension origin: ${info.extensionOrigin}, pipe Read: ${info.fdRead}, pipe write ${info.fdWrite} `);
- this.ReadAsync(info.fdRead, info.fdWrite)
- }
-
- onDisconnectNative(info: ConnectionInfo): void {
- hilog.info(DOMAIN_NUMBER, TAG, `onDisconnectNative, connectionId: ${info.connectionId}`);
- }
-
- onDestroy(): void {
- hilog.info(DOMAIN_NUMBER, TAG, 'onDestroy');
- }
- };
在工程Module的module.json5配置文件中注册WebNativeMessagingExtensionAbility组件。设置type标签为“webNativeMessaging”,srcEntry标签指向组件代码路径。
- {
- "module": {
- // ...
- "extensionAbilities": [
- {
- "name": "MyWebNativeMessageExtAbility",
- "description": "webNativeMessaging",
- "type": "webNativeMessaging",
- "exported": true,
- "srcEntry": "./ets/MyWebNativeMessageExtAbility/MyWebNativeMessageExtAbility.ets"
- }
- ]
- }
- }
在工程Module对应的module.json5配置文件中配置crossAppSharedConfig,定义共享配置项,共享配置文件需放置在工程resources/base/profile目录下,并通过$资源访问方式引用。
- {
- "module": {
- "crossAppSharedConfig": "$profile:shared_config"
- }
- }
6.在shared_config.json添加extension配置。
- {
- "crossAppSharedConfig": [
- // ...
- {
- // uri固定格式,datashareproxy://[包名]/browserNativeMessagingHosts,浏览器应用通过该uri获取的value,即extension配置。
- "uri": "datashareproxy://com.example.app/browserNativeMessagingHosts",
- // extension配置,格式参考extension配置章节的格式,注意转义字符
- "value": "{\"name\": \"com.example.myapplication\",\"description\": \"Send message to native app.\",\"abilityName\": \"MyWebNativeMessageExtAbility\", \"allowed_origins\":[\"chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/\"]}",
- "allowList": [
- // 允许访问的应用appIdentifier, 这里加入具体浏览器的appIdentifier
- "1234567890123456789"
- ]
- }
- ]
- }
浏览器负责实现扩展runtime接口,拉起WebNativeMessagingExtensionAbility,建立和管理NativeMessaging连接。需要申请权限:ohos.permission.WEB_NATIVE_MESSAGING。
当接收到创建NativeMessaging连接时,先通过get20获取目标应用的extension配置。然后读取WebNativeMessagingExtensionAbility名称和允许访问的扩展列表。最后校验是否允许访问。
- import { dataShare } from '@kit.ArkData';
-
- interface ExtensionConfig {
- abilityName:string;
- allowed_origins:string[];
- }
-
- async function getManifestData(bundleName:string, connectExtensionOrigin:string) {
- try {
- // 调用dataShare接口获取extension配置
- const dsProxyHelper = await dataShare.createDataProxyHandle();
- const urisToGet = [`datashareproxy://${bundleName}/browserNativeMessagingHosts`];
- const config : dataShare.DataProxyConfig = {
- type: dataShare.DataProxyType.SHARED_CONFIG,
- };
- const results = await dsProxyHelper.get(urisToGet, config);
- let foundValid = false;
- for (let i = 0; i < results.length; i++) {
- try {
- const result = results[i];
- const json = result.value;
- if (typeof json !== "string") {
- continue;
- }
- let jsonStr:string = json as string;
- let info:ExtensionConfig = JSON.parse(jsonStr);
- if (info.abilityName) {
- console.info('Native message json info is ok');
- if (!Array.isArray(info.allowed_origins)) {
- info.allowed_origins = [info.allowed_origins];
- }
- if (!info.allowed_origins.includes(connectExtensionOrigin)) {
- console.error('Origin not allowed, continue searching');
- continue;
- }
- foundValid = true;
- break;
- }
- } catch (error) {
- console.error('NativeMessage JSON parse error:', error);
- }
- }
- if (!foundValid) {
- console.error('NativeMessage JSON no valid manifest found');
- } else {
- console.info('NativeMessage allowed_origins match ok');
- }
- } catch (error) {
- console.error('Error getting config:', error);
- }
- }
调用webNativeMessagingExtensionManager.connectNative创建NativeMessaging连接,如WebNativeMessagingExtensionAbility尚未运行,该接口则会拉起ExtensionAbility并触发。
- import { UIAbility, Want, common } from '@kit.AbilityKit';
- import { webNativeMessagingExtensionManager } from '@kit.ArkWeb'
-
- class ConnectionCallback implements webNativeMessagingExtensionManager.WebExtensionConnectionCallback {
- onConnect(connection:webNativeMessagingExtensionManager.ConnectionNativeInfo) {
- // connected
- console.error(`onConnect id ${connection.connectionId} is connected`);
- }
- onDisconnect(connection:webNativeMessagingExtensionManager.ConnectionNativeInfo) {
- // disconnect
- console.error(`onDisconnect id ${connection.connectionId} is disconnected`);
- }
- onFailed(code:webNativeMessagingExtensionManager.NmErrorCode, errMsg:string) {
- console.error(`onFailed error code is ${code}, errMsg is ${errMsg}`);
- }
- }
-
- function connectNative(abilityContext: common.UIAbilityContext, bundleName: string, abilityName: string,
- connectExtensionOrigin: string, readPipe: number, writePipe: number) : void {
- try {
- let wantInfo:Want = {
- bundleName: bundleName,
- abilityName: abilityName,
- parameters: {
- 'ohos.arkweb.messageReadPipe': { 'type': 'FD', 'value': readPipe },
- 'ohos.arkweb.messageWritePipe': { 'type': 'FD', 'value': writePipe },
- 'ohos.arkweb.extensionOrigin': connectExtensionOrigin
- },
- };
-
- let options : ConnectionCallback = new ConnectionCallback;
- let connectId = webNativeMessagingExtensionManager.connectNative(abilityContext, wantInfo, options);
- console.info(`innerWebNativeMessageManager connectionId : ${connectId}` );
- } catch (error) {
- console.info(`inner callback error Message: ${JSON.stringify(error)}`);
- }
- }
需要销毁NativeMessaging连接时,调用webNativeMessagingExtensionManager.disconnectNative。
- import { webNativeMessagingExtensionManager } from '@kit.ArkWeb'
-
- function disconnectNative(connectId: number) : void {
- console.info(`NativeMessageDisconnect start connectionId is ${connectId}`);
- webNativeMessagingExtensionManager.disconnectNative(connectId);
- }