一、前言:为什么 JS ↔ C++ 通信是浏览器内核的核心问题
在很多人眼里,浏览器开发似乎分得很清楚:
-
前端:HTML / CSS / JavaScript
-
后端:C++ / 网络 / 系统能力
但真正做过浏览器内核开发的人都清楚一件事:
浏览器是一个"前端驱动后端"的系统
几乎所有用户行为,最终都是从 JS 开始的:
-
点击设置页的一个按钮
-
打开 chrome://extensions
-
触发下载、文件选择、系统配置
-
企业定制浏览器中调用本地能力
而这些行为,最终都必须落到 C++ 层才能完成。
因此,一个问题贯穿了整个浏览器内核设计:
JavaScript 是如何安全、可控、高性能地调用 C++ 的?
在 Chromium 的发展历史中,这个问题并没有一个"一步到位"的答案,而是逐步演进,形成了多种 JS ↔ C++ 通信方式并存的局面。
本文将从浏览器内核工程师视角,系统性梳理:
Chromium 中 JS 与 C++ 通信的 7 种方式
并结合工程实践,给出选型与演进建议。
二、理解 JS ↔ C++ 通信之前:Chromium 的架构前提
在深入具体方案之前,必须先统一一个基础认知。
2.1 多进程架构是前提条件
Chromium 采用严格的多进程模型:
几个关键结论:
-
JavaScript 永远运行在 Renderer 进程
-
绝大多数系统能力只存在于 Browser 进程
-
JS ↔ C++ 的本质 = 跨进程通信(IPC)
2.2 所谓"通信方式"的本质差异是什么?
所有 JS ↔ C++ 的方案,最终差异都体现在:
-
JS 从哪里调用
-
C++ 如何暴露接口
-
中间通信通道是什么
-
安全模型是否明确
-
是否符合 Chromium 的长期演进方向
三、JS ↔ C++ 通信方式全景:7 种方式总览
在 Chromium / 桌面浏览器语境下,完整的 7 种方式如下:
| 编号 | 方式 | 是否直接 JS → C++ |
|---|---|---|
| 1 | Web 标准 API | 是 |
| 2 | Chrome Extensions API | 是 |
| 3 | window.external(V8 注入) | 是 |
| 4 | Chrome WebUI(chrome.send) | 是 |
| 5 | Mojo WebUI / Mojo JS Bindings | 是 |
| 6 | postMessage(JS ↔ JS 间接) | 否 |
| 7 | WebView / CEF 宿主桥 | 是(非 Chromium 主线) |
下面逐一展开。
四、方式一:Web 标准 API(最"正统"的通信)
4.1 示例
navigator.clipboard.readText(); navigator.geolocation.getCurrentPosition(...)
4.2 内部调用链(简化)
JS → Blink WebIDL → Renderer C++ → Mojo → Browser Service → OS
4.3 工程评价
优点:
-
完全标准化
-
安全模型成熟
-
生命周期与页面一致
缺点:
-
能力受限
-
无法满足浏览器私有需求
📌 结论:
Web API 是浏览器对 Web 的承诺
不是浏览器给自己用的扩展能力
五、方式二:Chrome Extensions API(扩展通信)
5.1 示例
chrome.runtime.sendMessage({ cmd: 'readFile' });
5.2 调用链
Extension JS → ExtensionBindings → Mojo → Browser Extension System
5.3 特点
-
权限通过 manifest.json 声明
-
生命周期独立于页面
-
IPC 封装完善
📌 结论:
适合扩展生态
不适合浏览器内置页面(chrome://)
六、方式三:window.external(历史遗产)
6.1 JS 调用
window.external.ReadFile("C:\\test.txt");
6.2 C++ 注入(Renderer)
void ChromeRenderFrameObserver::DidClearWindowObject() { v8::Local<v8::Object> external = v8::Object::New(isolate); external->Set(context, v8_str("ReadFile"), v8::Function::New(context, ReadFileCallback)); global->Set(context, v8_str("external"), external); }
6.3 工程评价
| 优点 | 缺点 |
|---|---|
| 实现简单 | 非标准 |
| 灵活 | 安全边界模糊 |
| 兼容历史 | 难以维护 |
📌 结论:
window.external 是"能用但不该再扩展"的方案
七、方式四:Chrome WebUI(官方推荐方案)
7.1 JS 侧
chrome.send('readFile', ['path']);
7.2 C++ 侧
web_ui()->RegisterMessageCallback( "readFile", base::BindRepeating(&Handler::ReadFile, base::Unretained(this)));
7.3 回调 JS
ResolveJavascriptCallback(callback_id, base::Value(result));
7.4 特点
-
Chrome 内置页面广泛使用
-
权限模型清晰
-
生命周期受控
📌 结论:
WebUI 是 Chromium 当前阶段最稳妥的 JS ↔ C++ 通信方案
八、方式五:Mojo WebUI(未来方向)
8.1 Mojo IDL
module browser.mojom; interface FileService { ReadFile(string path) => (string content); };
8.2 C++ 实现
class FileServiceImpl : public mojom::FileService { public: void ReadFile(const std::string& path, ReadFileCallback callback) override; };
8.3 JS 调用
import {FileService} from './file_service.mojom-webui.js'; const service = FileService.getRemote(); const {content} = await service.readFile(path);
8.4 为什么 Mojo 是未来
-
强类型接口
-
自动生成 JS / C++
-
IPC 原生支持
-
安全性和可维护性极高
📌 结论:
Mojo WebUI 是 Chromium 官方长期演进方向
九、方式六:postMessage(间接通信)
9.1 示例
window.postMessage({ type: 'CMD' }, '*');
9.2 关键事实
❌ postMessage 永远不会直接进入 C++
真实路径是:
JS → JS →(WebUI / Extension)→ C++
📌 结论:
postMessage 是 JS ↔ JS 的工具
不是 JS ↔ C++ 的解决方案
十、方式七:WebView / CEF 宿主桥
10.1 JS
window.chrome.webview.postMessage({ cmd: 'readFile' });
10.2 C++(宿主)
webview->add_WebMessageReceived(...);
10.3 架构定位
-
属于宿主应用
-
不属于 Chromium 浏览器自身
-
不走 Browser / Renderer 模型
📌 结论:
WebView / CEF 是"浏览器被嵌入"时的方案
不是浏览器内核的通信主线
十一、7 种方式工程对比总结
| 方式 | 官方 | 安全 | 可维护 | 推荐度 |
|---|---|---|---|---|
| Web API | 是 | 高 | 高 | 低 |
| Extension API | 是 | 高 | 中 | 中 |
| window.external | 否 | 低 | 低 | 不推荐 |
| WebUI | 是 | 高 | 高 | 推荐 |
| Mojo WebUI | 是 | 极高 | 极高 | 强烈推荐 |
| postMessage | 是 | 中 | 中 | 不适合 |
| WebView Bridge | 否 | 中 | 中 | 不适合 |
十二、给定制浏览器的最终建议
结合 Chrome 132 / 360 浏览器等场景:
-
历史能力:window.external(只维护,不新增)
-
现有内置页面:WebUI
-
所有新能力:Mojo WebUI
这是安全、可维护、符合 Chromium 演进方向的最优解。
十三、结语
JS ↔ C++ 通信方式的演进,本质上是:
浏览器从"功能堆砌"走向"系统级平台"的过程
理解这些通信机制,不只是为了"把功能跑通",
而是为了写出能长期维护、能持续演进的内核代码。