从 window.external 到 Mojo深度解析 Chromium 中 JS 与 C++ 的 7 种通信机制

一、前言:为什么 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++ 的方案,最终差异都体现在:

  1. JS 从哪里调用

  2. C++ 如何暴露接口

  3. 中间通信通道是什么

  4. 安全模型是否明确

  5. 是否符合 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++ 通信方式的演进,本质上是:

浏览器从"功能堆砌"走向"系统级平台"的过程

理解这些通信机制,不只是为了"把功能跑通",

而是为了写出能长期维护、能持续演进的内核代码

相关推荐
To_OC6 小时前
LC 131 分割回文串:刚学回溯时,我连怎么切字符串都想不明白
javascript·算法·leetcode
To_OC7 小时前
LC 42 接雨水:暴力超时卡半天?前后缀数组一用就通了
javascript·算法·leetcode
小林ixn7 小时前
从 ??= 到 onKeyDown:一个 React 组件的“自我修养”
前端·javascript·react.js
এ慕ོ冬℘゜8 小时前
前端实战:jQuery 多条件联合搜索(标题模糊查询 + 日历时间段筛选)
前端·javascript·jquery
随心点儿9 小时前
js postMessage 实现跨域发送消息-应用示例
javascript·ecmascript·postmessage
kyriewen10 小时前
我让AI改一个bug——它偷偷动了5个我没让它碰的地方
前端·javascript·ai编程
leoZ23112 小时前
记忆系统与 Agent 定制完全指南(四):自定义 Agent 开发(一)
前端·chrome
leoZ23112 小时前
记忆系统与 Agent 定制完全指南(三):记忆的检索与使用
前端·chrome
gis开发之家14 小时前
《Vue3 从入门到大神35篇》Vue3 源码详解(五):effect 依赖收集原理——track 与 trigger 是如何工作的?
javascript·typescript·前端框架·vue3·vue3源码
橘子星16 小时前
我一个前端切图仔,凭什么能在浏览器里跑大模型?
前端·javascript·前端框架