原生消息传递

扩展程序可以使用类似于其他消息传递 API 的 API 与原生应用交换消息。支持此功能的原生应用必须注册可与扩展程序通信的原生消息传递主机。Chrome 会在单独的进程中启动主机,并使用标准输入和标准输出流与其通信。

原生消息传递主机

如需注册原生消息传递主机,应用必须保存一个用于定义原生消息传递主机配置的文件。

该文件的示例如下:

{
  "name": "com.my_company.my_application",
  "description": "My Application",
  "path": "C:\\Program Files\\My Application\\chrome_native_messaging_host.exe",
  "type": "stdio",
  "allowed_origins": ["chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/"]
}

原生消息传递主机清单文件必须是有效的 JSON,并且包含以下字段:

name
原生消息传递主机的名称。客户端将此字符串传递给 runtime.connectNative()runtime.sendNativeMessage()。此名称只能包含小写字母数字字符、下划线和英文句点。名称不能以英文句点开头或结尾,并且英文句点后面不能跟另一个英文句点。
description
简短的应用说明。
path
原生消息传递主机二进制文件的路径。在 Linux 和 macOS 上,路径必须是绝对路径。在 Windows 上,它可以相对于包含清单文件的目录。启动宿主进程时,当前目录设置为包含宿主二进制文件的目录。例如,如果此参数设置为 C:\Application\nm_host.exe,则会从当前目录“C:\Application”启动。
type
用于与原生消息传递主机通信的接口类型。此参数只有一个可能的值:stdio。它表示 Chrome 应使用 stdinstdout 与主机通信。
allowed_origins
应有权访问原生消息传递主机的扩展程序列表。allowed_origins不能包含通配符。

原生消息传递主机位置

清单文件的位置取决于平台。

Windows 上,清单文件可以位于文件系统中的任何位置。应用安装程序必须创建注册表项(HKEY_LOCAL_MACHINE\SOFTWARE\Google\Chrome\NativeMessagingHosts\com.my_company.my_applicationHKEY_CURRENT_USER\SOFTWARE\Google\Chrome\NativeMessagingHosts\com.my_company.my_application),并将该项的默认值设置为清单文件的完整路径。例如,使用以下命令:

REG ADD "HKCU\Software\Google\Chrome\NativeMessagingHosts\com.my_company.my_application" /ve /t REG_SZ /d "C:\path\to\nmh-manifest.json" /f

或者使用以下 .reg 文件:

Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Google\Chrome\NativeMessagingHosts\com.my_company.my_application]
@="C:\\path\\to\\nmh-manifest.json"

当 Chrome 查找原生消息传递主机时,会先查询 32 位注册表,然后再查询 64 位注册表。

macOSLinux 上,原生消息传递宿主的清单文件的位置因浏览器(Google Chrome、Google Chrome for Testing 或 Chromium)而异。系统级原生即时通讯主机在固定位置查找,而用户级原生即时通讯主机在用户个人资料目录NativeMessagingHosts/ 子目录中查找。

macOS(系统级)
Google Chrome:/Library/Google/Chrome/NativeMessagingHosts/com.my_company.my_application.json
Google Chrome for Testing:/Library/Google/ChromeForTesting/NativeMessagingHosts/com.my_company.my_application.json
Chromium:/Library/Application Support/Chromium/NativeMessagingHosts/com.my_company.my_application.json
macOS(用户专用,默认路径)
Google Chrome:~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.my_company.my_application.json
Google Chrome for Testing:~/Library/Application Support/Google/ChromeForTesting/NativeMessagingHosts/com.my_company.my_application.json
Chromium:~/Library/Application Support/Chromium/NativeMessagingHosts/com.my_company.my_application.json
Linux(系统级)
Google Chrome:/etc/opt/chrome/native-messaging-hosts/com.my_company.my_application.json
Google Chrome for Testing:/etc/opt/chrome_for_testing/native-messaging-hosts/com.my_company.my_application.json
Chromium:/etc/chromium/native-messaging-hosts/com.my_company.my_application.json
Linux(特定于用户,默认路径)
Google Chrome:~/.config/google-chrome/NativeMessagingHosts/com.my_company.my_application.json
Google Chrome for Testing:~/.config/google-chrome-for-testing/NativeMessagingHosts/com.my_company.my_application.json
Chromium:~/.config/chromium/NativeMessagingHosts/com.my_company.my_application.json

原生消息传递协议

Chrome 会在单独的进程中启动每个原生消息传递主机,并使用标准输入 (stdin) 和标准输出 (stdout) 与其通信。双向发送消息时使用相同的格式;每条消息都使用 JSON 进行序列化、使用 UTF-8 进行编码,并且前面带有以原生字节顺序表示的 32 位消息长度。来自原生消息传递主机的单个消息的大小上限为 1 MB,这主要是为了保护 Chrome 免受行为不当的原生应用的侵害。发送给原生消息传递主机 的消息大小上限为 64 MiB。

原生消息传递主机的第一个参数是调用方的源,通常为 chrome-extension://[ID of allowed extension]。这样一来,当原生消息传递主机清单allowed_origins 键中指定了多个扩展程序时,原生消息传递主机便可以识别消息的来源。

在 Windows 上,原生消息传递主机还会收到一个命令行实参,其中包含对调用 Chrome 原生窗口的句柄:--parent-window=<decimal handle value>。这样,原生消息传递主机便可创建正确设置了父级的原生界面窗口。请注意,如果调用上下文是 Service Worker,则此值为 0。

当使用 runtime.connectNative() 创建消息传递端口时,Chrome 会启动原生消息传递宿主进程,并使其保持运行状态,直到该端口被销毁。另一方面,如果使用 runtime.sendNativeMessage() 发送消息,而不创建消息传递端口,Chrome 会为每条消息启动一个新的原生消息传递主机进程。在这种情况下,主机进程生成的第一个消息将作为对原始请求的响应进行处理,并且 Chrome 会将其传递给调用 runtime.sendNativeMessage() 时指定的回调。在这种情况下,本地消息传递主机生成的所有其他消息都会被忽略。

连接到原生应用

向原生应用发送消息以及从原生应用接收消息与跨扩展程序的消息传递非常相似。主要区别在于,使用 runtime.connectNative() 而不是 runtime.connect(),以及使用 runtime.sendNativeMessage() 而不是 runtime.sendMessage()

如需使用这些方法,必须在扩展程序的清单文件中声明“nativeMessaging”权限。

这些方法在内容脚本中不可用,仅在扩展程序的页面和 Service Worker 中可用。如需从内容脚本与原生应用通信,请将消息发送到您的 Service Worker,以便将其传递给原生应用。

以下示例创建了一个连接到原生消息传递主机 com.my_company.my_applicationruntime.Port 对象,开始监听来自该端口的消息并发送一条传出消息:

const port = chrome.runtime.connectNative('com.my_company.my_application');
port.onMessage.addListener((msg) => {
  console.log('Received', msg);
});
port.onDisconnect.addListener(() => {
  if (chrome.runtime.lastError) {
    console.error(
      'Disconnected due to error:',
      chrome.runtime.lastError.message
    );
  } else {
    console.log('Disconnected');
  }
});
port.postMessage({text: 'Hello, my_application'});

使用 runtime.sendNativeMessage 向原生应用发送消息,而无需创建端口,例如:

chrome.runtime.sendNativeMessage(
  'com.my_company.my_application',
  {text: 'Hello'},
  (response) => {
    if (chrome.runtime.lastError) {
      console.error(
        'Error sending native message:',
        chrome.runtime.lastError.message
      );
      return;
    }
    console.log('Received', response);
  }
);

调试原生消息传递

发生原生消息传递失败时,诊断输出会写入 Chrome 的错误日志。

Linux 和 macOS

# Linux
google-chrome --enable-logging=stderr --log-level=1 2>&1 | \
  grep -E "native_messag|launch_context"

# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
  --enable-logging=stderr --log-level=1 2>&1 | \
  grep -E "native_messag|launch_context"

Windows

启动 Chrome 并启用日志记录

chrome.exe --enable-logging --log-level=1

传递 --user-data-dir="%TEMP%\nm-debug" 以启动一个单独的实例,而不附加到现有的 Chrome 进程。

如需查看输出,请使用 PowerShell 流式传输 chrome_debug.log

$log = "$env:LOCALAPPDATA\Google\Chrome\User Data\chrome_debug.log"
Get-Content -Wait $log | Select-String "native_messag|launch_context"

您还可以在文本编辑器中打开 chrome_debug.log,然后搜索 launch_context.ccnative_message_process_host.cc

关键日志记录详细信息

  • launch_context.cc 中的清单查找和解析失败会记录为警告。使用 --log-level=1 而不是 2 (ERROR),后者会抑制这些启动诊断信息。
  • 搜索 launch_context 可查找清单和二进制文件启动错误,搜索 native_messag 可查找载荷大小和管道通信错误。

常见错误

以下是一些常见错误以及解决这些错误的提示:

未能启动原生消息传递主机。

检查您是否拥有执行原生消息传递主机文件的足够权限。

指定的原生消息传递主机名无效。

检查名称是否包含无效字符。仅允许使用小写字母数字字符、下划线和英文句点。名称不能以点开头或结尾,并且点后面不能跟另一个点。

原生主机已退出。

在 Chrome 读取消息之前,与原生消息传递主机的管道已中断。这很可能是从您的原生消息传递主机发起的。

找不到指定的原生消息传递主机。

请检查以下事项:

  • 扩展程序和清单文件中的名称拼写是否正确?
  • 在 Windows 上,注册表项是否存在于 HKEY_CURRENT_USERHKEY_LOCAL_MACHINE 下,并且其默认值是否指向完整的清单路径?Chrome 会先查询 32 位注册表视图,然后再查询 64 位视图。 使用 regedit 验证密钥。请参阅原生消息传递主机位置
  • 在 macOS 和 Linux 上,清单文件是否位于预期目录中,并以主机命名(例如 com.my_company.my_application.json)?请参阅原生消息传递主机位置
  • 清单文件是否采用正确的格式?具体来说,JSON 是否有效且格式正确,并且值是否与原生消息传递主机清单的定义相符?
  • path 中指定的文件是否存在?在 Windows 上,路径可以是相对路径,但在 macOS 和 Linux 上,路径必须是绝对路径。

禁止访问指定的原生消息传递主机。

扩展程序的来源是否列在 allowed_origins 中?

与原生消息传递主机通信时出错。

这表示原生消息传递主机中的通信协议实现不正确。

  • 确保 stdout 中的所有输出都遵循原生消息传递协议。如果您想出于调试目的打印一些数据,请写入到 stderr
  • 确保 32 位消息长度采用的是平台的原生整数格式(小端序/大端序)。
  • 消息长度不得超过 1024*1024。
  • 消息大小必须等于消息中的字节数。这可能与字符串的“长度”不同,因为字符可能由多个字节表示。
  • 仅限 Windows:确保程序的 I/O 模式设置为 O_BINARY。默认情况下,I/O 模式为 O_TEXT,这会损坏消息格式,因为换行符 (\n = 0A) 会替换为 Windows 样式的行尾 (\r\n = 0D 0A)。可以使用 __setmode 设置 I/O 模式。