跳到主要内容

证书透明度修复

·2 分钟阅读

Electron 1.4.12 包含一个重要的补丁,修复了一个上游 Chrome 问题,该问题导致某些 Symantec、GeoTrust 和 Thawte SSL/TLS 证书在 libchromiumcontent(Electron 底层的 Chrome 库)构建时间起 10 周后被错误地拒绝。受影响的网站使用的证书本身没有问题,更换这些证书也无济于事。


在 Electron 1.4.0 — 1.4.11 版本中,使用这些受影响证书的网站的 HTTPS 请求将在某个日期后因网络错误而失败。这影响了使用 Chrome 底层网络 API 发出的 HTTPS 请求,例如 window.fetch、Ajax 请求、Electron 的 net API、BrowserWindow.loadURLwebContents.loadURL<webview> 标签上的 src 属性等。

将您的应用升级到 1.4.12 版本将阻止这些请求失败的发生。

注意:此问题在 Chrome 53 中引入,因此早于 1.4.0 的 Electron 版本不受影响。

影响日期

下表列出了每个 Electron 1.4 版本以及使用这些受影响证书的网站请求何时开始失败的日期。

Electron 版本影响日期
1.3.x不受影响
1.4.0已开始失败
1.4.1已开始失败
1.4.2已开始失败
1.4.32016年12月10日 晚上9:00 PST
1.4.42016年12月10日 晚上9:00 PST
1.4.52016年12月10日 晚上9:00 PST
1.4.62017年1月14日 晚上9:00 PST
1.4.72017年1月14日 晚上9:00 PST
1.4.82017年1月14日 晚上9:00 PST
1.4.92017年1月14日 晚上9:00 PST
1.4.102017年1月14日 晚上9:00 PST
1.4.112017年2月11日 晚上9:00 PST
1.4.12不受影响

您可以通过将计算机时钟调整到未来的日期,然后检查是否能成功加载 https://symbeta.symantec.com/welcome/ 来验证您的应用的影响日期。

更多信息

您可以在以下地方阅读有关此主题、原始问题和修复的更多信息

2016 年 9 月:新应用

·3 分钟阅读

以下是九月份添加到网站的新 Electron 应用和讲座。


本网站通过社区的 pull request 添加新的 应用聚会。您可以 关注仓库 以获取新添加内容的通知,如果您对网站的 所有 更改不感兴趣,请订阅 博客 RSS feed

如果您开发了 Electron 应用或举办了聚会,请提交一个 pull request 将其添加到网站,它将出现在下一期汇总中。

新讲座

九月份,GitHub 举办了 GitHub Universe 大会,被誉为软件未来建设者的盛会。本次活动中有几个有趣的 Electron 讲座。

此外,如果您恰好在 12 月 5 日身处巴黎,Zeke 将在 dotJS 2016 上进行 Electron 讲座

新应用

Pexels搜索完全免费的图片并将其复制到剪贴板
Timestamp一个更好的 macOS 菜单栏时钟,具有可定制的日期/时间显示和日历
Harmony与 Spotify、Soundcloud、Play Music 和本地文件兼容的音乐播放器
uPhoneWebRTC 桌面电话
SealTalk由融云即时通讯云服务和 IM SDK 提供支持的即时通讯应用
Infinity一种制作演示文稿的简单方法
Cycligent Git Tool用于 Git 项目的简单直观的图形界面
Foco使用 Foco 保持专注,提高工作效率
Strawberry赢得终身食客。使用一体化餐厅软件套件更好地了解并服务他们。
Mixmax实时查看您电子邮件上的所有操作。随时随地撰写邮件。
Firebase Admin一个 Firebase 数据管理工具
ANote一个简单友好的 Markdown 笔记应用
Temps一个简单但智能的菜单栏天气应用
Amium一个将对话引入您文件的工作协作产品
Soube简单的音乐播放器
(Un)colored下一代桌面富文本编辑器,支持按主题保存文档,兼容 HTML 和 Markdown。适用于 Windows、OS X 和 Linux。
quickcalc菜单栏计算器
Forestpin Analytics企业财务数据分析工具
LingREST 客户端
Shortexts用于您频繁复制的文本、文件夹和表情符号的快捷方式
Front-End Box一套前端代码生成器

结构化数据的 Electron API 文档

·3 分钟阅读

今天我们宣布对 Electron 文档进行一些改进。现在每个新版本都包含一个 JSON 文件,详细描述了 Electron 的所有公共 API。我们创建此文件是为了让开发者能够以有趣的新方式使用 Electron 的 API 文档。


Schema 概览

每个 API 都是一个对象,包含 name、description、type 等属性。像 BrowserWindowMenu 这样的类还有描述其实例方法、实例属性、实例事件等的附加属性。

这是描述 BrowserWindow 类的 schema 摘录

{
name: 'BrowserWindow',
description: 'Create and control browser windows.',
process: {
main: true,
renderer: false
},
type: 'Class',
instanceName: 'win',
slug: 'browser-window',
websiteUrl: 'https://electron.js.cn/docs/api/browser-window',
repoUrl: 'https://github.com/electron/electron/blob/v1.4.0/docs/api/browser-window.md',
staticMethods: [...],
instanceMethods: [...],
instanceProperties: [...],
instanceEvents: [...]
}

这里是一个方法描述的示例,本例中是 apis.BrowserWindow.instanceMethods.setMaximumSize 实例方法

{
name: 'setMaximumSize',
signature: '(width, height)',
description: 'Sets the maximum size of window to width and height.',
parameters: [{
name: 'width',
type: 'Integer'
}, {
name: 'height',
type: 'Integer'
}]
}

使用新数据

为了方便开发者在其项目中使用这些结构化数据,我们创建了 electron-docs-api,这是一个小型 npm 包,每当有新的 Electron 版本发布时,它都会自动发布。

npm install electron-api-docs --save

为了即时体验,您可以在 Node.js REPL 中尝试这个模块

npm i -g trymodule && trymodule electron-api-docs=apis

数据如何收集

Electron 的 API 文档遵循 Electron 编码风格Electron 风格指南,因此其内容可以程序化地解析。

electron-docs-linterelectron/electron 仓库的一个新的开发依赖。它是一个命令行工具,用于检查所有 markdown 文件并强制执行风格指南的规则。如果发现错误,会列出错误并暂停发布过程。如果 API 文档有效,则会创建 electron-json.api 文件,并作为 Electron 发布的一部分 上传到 GitHub

Standard JavaScript 和 Standard Markdown

今年早些时候,Electron 的代码库更新为对所有 JavaScript 使用 standard linter。Standard 的 README 总结了选择此工具的原因

采用 standard 风格意味着将代码清晰度和社区惯例的重要性置于个人风格之上。这对于所有项目和开发文化来说可能并不完全适用,然而,开源对于新手来说可能是一个不太友好的地方。建立清晰、自动化的贡献者期望可以使项目更健康。

我们最近还创建了 standard-markdown,用于验证我们文档中的所有 JavaScript 代码片段是否有效,并与代码库本身的风格保持一致。

这些工具共同帮助我们使用持续集成 (CI) 自动查找 pull request 中的错误。这减轻了人工进行代码审查的负担,并让我们对文档的准确性更有信心。

社区努力

Electron 的文档在不断改进,这得益于我们出色的开源社区。截至撰写本文时,已有近 300 人为文档做出了贡献。

我们很高兴看到大家将如何使用这些新的结构化数据。可能的用途包括

Electron 内部原理:弱引用

·6 分钟阅读

作为一种具有垃圾回收机制的语言,JavaScript 让用户无需手动管理资源。但由于 Electron 托管了这种环境,它必须非常小心地避免内存和资源泄漏。

本文介绍了弱引用的概念以及如何在 Electron 中使用它们来管理资源。


弱引用

在 JavaScript 中,每当您将一个对象赋值给一个变量时,您就为该对象添加了一个引用。只要该对象存在引用,它就会一直保留在内存中。一旦该对象的所有引用都消失了,即没有变量再存储该对象时,JavaScript 引擎将在下一次垃圾回收时回收内存。

弱引用是对一个对象的引用,它允许您获取该对象,同时不影响该对象是否会被垃圾回收。当对象被垃圾回收时,您还会收到通知。这样就可以使用 JavaScript 管理资源了。

以 Electron 中的 NativeImage 类为例,每次调用 nativeImage.create() API 时,都会返回一个 NativeImage 实例,并在 C++ 中存储图像数据。一旦您不再使用该实例且 JavaScript 引擎 (V8) 对该对象进行了垃圾回收,就会调用 C++ 中的代码来释放内存中的图像数据,因此用户无需手动管理。

另一个例子是 窗口消失问题,它直观地展示了当窗口的所有引用都消失时,窗口是如何被垃圾回收的。

在 Electron 中测试弱引用

在原生 JavaScript 中无法直接测试弱引用,因为该语言没有分配弱引用的方法。JavaScript 中唯一与弱引用相关的 API 是 WeakMap,但由于它只创建弱引用键,因此无法知道对象何时被垃圾回收。

在 Electron v0.37.8 之前的版本中,您可以使用内部的 v8Util.setDestructor API 来测试弱引用,它会为传入的对象添加一个弱引用,并在对象被垃圾回收时调用回调函数

// Code below can only run on Electron < v0.37.8.
var v8Util = process.atomBinding('v8_util');

var object = {};
v8Util.setDestructor(object, function () {
console.log('The object is garbage collected');
});

// Remove all references to the object.
object = undefined;
// Manually starts a GC.
gc();
// Console prints "The object is garbage collected".

注意,您必须使用 --js-flags="--expose_gc" 命令开关启动 Electron 才能暴露内部的 gc 函数。

该 API 在后续版本中被移除,因为 V8 实际上不允许在析构函数中运行 JavaScript 代码,并且在后续版本中这样做会导致随机崩溃。

remote 模块中的弱引用

除了使用 C++ 管理原生资源外,Electron 还需要弱引用来管理 JavaScript 资源。一个例子是 Electron 的 remote 模块,这是一个 远程过程调用 (RPC) 模块,允许从渲染进程中使用主进程中的对象。

remote 模块的一个关键挑战是避免内存泄漏。当用户在渲染进程中获取一个远程对象时,remote 模块必须保证该对象在主进程中继续存在,直到渲染进程中的引用消失。此外,它还必须确保当渲染进程中不再有对该对象的任何引用时,该对象能够被垃圾回收。

例如,如果实现不当,以下代码会很快导致内存泄漏

const { remote } = require('electron');

for (let i = 0; i < 10000; ++i) {
remote.nativeImage.createEmpty();
}

remote 模块中的资源管理很简单。每当请求一个对象时,会向主进程发送一条消息,Electron 会将该对象存储在一个 map 中并为其分配一个 ID,然后将 ID 发送回渲染进程。在渲染进程中,remote 模块会收到 ID 并用一个代理对象对其进行封装,当代理对象被垃圾回收时,会向主进程发送一条消息以释放该对象。

remote.require API 为例,简化实现如下所示

remote.require = function (name) {
// Tell the main process to return the metadata of the module.
const meta = ipcRenderer.sendSync('REQUIRE', name);
// Create a proxy object.
const object = metaToValue(meta);
// Tell the main process to free the object when the proxy object is garbage
// collected.
v8Util.setDestructor(object, function () {
ipcRenderer.send('FREE', meta.id);
});
return object;
};

在主进程中

const map = {};
const id = 0;

ipcMain.on('REQUIRE', function (event, name) {
const object = require(name);
// Add a reference to the object.
map[++id] = object;
// Convert the object to metadata.
event.returnValue = valueToMeta(id, object);
});

ipcMain.on('FREE', function (event, id) {
delete map[id];
});

带有弱值的 Map

在之前的简单实现中,remote 模块中的每次调用都会从主进程返回一个新的远程对象,每个远程对象代表主进程中该对象的一个引用。

设计本身没有问题,但问题在于当多次调用以接收同一个对象时,会创建多个代理对象,对于复杂对象,这会给内存使用和垃圾回收带来巨大压力。

例如,以下代码

const { remote } = require('electron');

for (let i = 0; i < 10000; ++i) {
remote.getCurrentWindow();
}

它首先花费大量内存创建代理对象,然后占用 CPU (中央处理器) 进行垃圾回收并发送 IPC 消息。

一个显而易见的优化是缓存远程对象:当已经存在一个具有相同 ID 的远程对象时,将返回先前的远程对象,而不是创建新的对象。

使用 JavaScript 核心中的 API 无法做到这一点。使用普通 map 缓存对象会阻止 V8 对对象进行垃圾回收,而 WeakMap 类只能使用对象作为弱键。

为了解决这个问题,添加了一种以弱引用作为值的 map 类型,它非常适合缓存带有 ID 的对象。现在 remote.require 看起来像这样

const remoteObjectCache = v8Util.createIDWeakMap()

remote.require = function (name) {
// Tell the main process to return the meta data of the module.
...
if (remoteObjectCache.has(meta.id))
return remoteObjectCache.get(meta.id)
// Create a proxy object.
...
remoteObjectCache.set(meta.id, object)
return object
}

请注意,remoteObjectCache 将对象存储为弱引用,因此当对象被垃圾回收时,无需删除键。

原生代码

对于对 Electron 中弱引用的 C++ 代码感兴趣的人,可以在以下文件中找到

setDestructor API

createIDWeakMap API

2016 年 8 月:新应用

·3 分钟阅读

以下是八月份添加到网站的新 Electron 应用。


本网站通过社区的 pull request 添加新的 应用聚会。您可以 关注仓库 以获取新添加内容的通知,如果您对网站的 所有 更改不感兴趣,请订阅 博客 RSS feed

如果您开发了 Electron 应用或举办了聚会,请提交一个 pull request 将其添加到网站,它将出现在下一期汇总中。

新应用

Code RPGifyRPG 风格的编程应用
PamFax一个用于发送和接收传真的跨平台应用
BlankUp清晰度 +1 的 Markdown 编辑器
Rambox免费开源的即时通讯和电子邮件应用,将常用网络应用组合在一起
Gordie最适合您的卡片收藏的应用
Ionic Creator更快地构建出色的移动应用
TwitchAlerts用精美的警报和通知让您的观众满意
Museeks一个简单、干净、跨平台的音乐播放器
SeaPig一个 Markdown 到 HTML 的转换器
GroupMe非官方 GroupMe 应用
Moeditor您的全能 Markdown 编辑器
SoundnodeSoundnode 应用是桌面版的 Soundcloud
QMUI WebQMUI Web Desktop 是一个基于 QMUI Web Framework 的项目管理应用
Svgsus组织、清理和转换您的 SVG
Ramme非官方 Instagram 桌面应用
InsomniaREST API 客户端
Correo适用于 Windows、macOS 和 Linux 的菜单栏/任务栏 Gmail 应用
KongDashKong Admin API 桌面客户端
Translation Editor用于 INTL ICU 消息翻译文件的编辑器 (参见 formatjsio)
5EClient5EPlay CSGO 客户端
Theme Juice简化本地 WordPress 开发

辅助功能工具

·2 分钟阅读

开发可访问的应用非常重要,我们很高兴向 DevtronSpectron 引入新功能,让开发者有机会让他们的应用更好地为所有人服务。


Electron 应用中的辅助功能问题与网站类似,因为它们最终都是 HTML。然而,对于 Electron 应用,您不能使用在线资源进行辅助功能审计,因为您的应用没有可供审计工具指向的 URL。

这些新功能将这些审计工具带到您的 Electron 应用中。您可以选择使用 Spectron 将审计添加到您的测试中,或在 Devtron 的 DevTools 中使用它们。请继续阅读工具摘要,或查看我们的 辅助功能文档 了解更多信息。

Spectron

在测试框架 Spectron 中,您现在可以审计应用中的每个窗口和 <webview> 标签。例如

app.client.auditAccessibility().then(function (audit) {
if (audit.failed) {
console.error(audit.message);
}
});

您可以在 Spectron 的文档 中阅读有关此功能的更多信息。

Devtron

在 Devtron 中有一个新的辅助功能选项卡,它允许您审计应用中的页面,并对结果进行排序和过滤。

devtron screenshot

这两个工具都使用了 Google 为 Chrome 构建的 辅助功能开发者工具 库。您可以在该 仓库的 wiki 上了解更多关于该库使用的辅助功能审计规则的信息。

如果您知道 Electron 的其他优秀辅助功能工具,请提交 pull request 将其添加到 辅助功能文档 中。

npm install electron

·3 分钟阅读

从 Electron 1.3.1 版本开始,您可以使用 npm install electron --save-dev 在您的应用中安装最新预编译的 Electron 版本。


npm install electron

预编译的 Electron 二进制文件

如果您以前开发过 Electron 应用,您很可能遇到过 electron-prebuilt 这个 npm 包。这个包几乎是每个 Electron 项目不可或缺的一部分。安装时,它会检测您的操作系统,并下载一个预编译的二进制文件,该文件针对您的系统架构进行编译。

新名称

Electron 的安装过程对于新开发者来说常常是一个障碍。许多勇敢的人尝试通过运行 npm install electron 而不是 npm install electron-prebuilt 来开始开发 Electron 应用程序,结果却发现(通常是在经历了很多困惑之后)这并不是他们想要的那个 electron

这是因为在 GitHub 的 Electron 项目存在之前,npm 上已经有一个名为 electron 的现有项目。为了让新开发者更容易、更直观地进行 Electron 开发,我们联系了现有 electron npm 包的拥有者,询问他是否愿意让我们使用这个名称。幸运的是,他是我们项目的粉丝,并同意帮助我们重新利用这个名称。

Prebuilt 仍在继续

自版本 1.3.1 起,我们已开始同时向 npm 发布 electronelectron-prebuilt 包。这两个包是相同的。我们选择暂时继续以这两个名称发布包,以免给当前在项目中使用 electron-prebuilt 的数千名开发者带来不便。我们建议更新您的 package.json 文件以使用新的 electron 依赖,但我们将继续发布 electron-prebuilt 的新版本,直到 2016 年底。

electron-userland/electron-prebuilt 仓库将继续作为 electron npm 包的规范主页。

鸣谢

我们特别感谢 @mafintosh@maxogden 以及许多其他贡献者,感谢他们创建和维护了 electron-prebuilt,以及为 JavaScript、Node.js 和 Electron 社区做出的不懈贡献。

同时感谢 @logicalparadox 允许我们在 npm 上接管 electron 包。

更新你的项目

我们已与社区合作,更新了受此变化影响的常用包。例如 electron-packagerelectron-rebuildelectron-builder 等包已更新,可以使用新名称,同时继续支持旧名称。

如果你在安装这个新包时遇到任何问题,请通过在 electron-userland/electron-prebuilt 仓库中提出 issue 来告知我们。

对于 Electron 的其他任何问题,请使用 electron/electron 仓库。

Electron 内部原理:将 Node 用作库

·阅读时长约 4 分钟

这是解释 Electron 内部原理的系列文章中的第二篇。如果你还没有阅读过,请查阅关于事件循环集成的第一篇文章

大多数人使用 Node 进行服务器端应用程序开发,但由于 Node 丰富的 API 和蓬勃发展的社区,它也非常适合用作嵌入式库。本文解释了 Node 在 Electron 中如何作为库使用。


构建系统

Node 和 Electron 都使用 GYP 作为它们的构建系统。如果你想在你的应用中嵌入 Node,你也必须使用它作为构建系统。

刚接触 GYP?在继续阅读本文之前,请先阅读本指南

Node 的标志

Node 源代码目录中的 node.gyp 文件描述了 Node 的构建方式,以及许多 GYP 变量,这些变量控制着 Node 的哪些部分被启用以及是否打开某些配置。

要更改构建标志,你需要在项目的 .gypi 文件中设置变量。Node 中的 configure 脚本可以为你生成一些常见配置,例如运行 ./configure --shared 将生成一个 config.gypi 文件,其中包含指示 Node 构建为共享库的变量。

Electron 不使用 configure 脚本,因为它有自己的构建脚本。Node 的配置在 Electron 根源代码目录下的 common.gypi 文件中定义。

在 Electron 中,通过将 GYP 变量 node_shared 设置为 true,Node 被链接为共享库,因此 Node 的构建类型将从 executable 更改为 shared_library,并且包含 Node 的 main 入口点的源代码将不会被编译。

由于 Electron 使用 Chromium 自带的 V8 库,Node 源代码中包含的 V8 库不再使用。这通过将 node_use_v8_platformnode_use_bundled_v8 都设置为 false 来实现。

共享库还是静态库

当与 Node 链接时,有两种选择:你可以将 Node 构建为静态库并将其包含在最终可执行文件中,或者你可以将其构建为共享库并随最终可执行文件一起分发。

在很长一段时间里,Electron 将 Node 构建为静态库。这使得构建过程简单,可以实现最佳的编译器优化,并允许 Electron 在分发时无需额外的 node.dll 文件。

然而,在 Chrome 切换使用 BoringSSL 后,情况发生了变化。BoringSSL 是 OpenSSL 的一个分支,它移除了几个未使用的 API 并改变了许多现有接口。由于 Node 仍然使用 OpenSSL,如果它们链接在一起,编译器会因符号冲突而产生大量链接错误。

Electron 无法在 Node 中使用 BoringSSL,也无法在 Chromium 中使用 OpenSSL,因此唯一的选择是切换到将 Node 构建为共享库,并在各自的组件中隐藏 BoringSSL 和 OpenSSL 的符号

这一变化给 Electron 带来了一些积极的副作用。在此更改之前,如果你使用原生模块,就无法在 Windows 上重命名 Electron 的可执行文件,因为可执行文件的名称硬编码在导入库中。Node 被构建为共享库后,这个限制消失了,因为所有原生模块都链接到 node.dll,而它的名称无需更改。

支持原生模块

原生模块在 Node 中通过定义一个供 Node 加载的入口函数来工作,然后从 Node 中搜索 V8 和 libuv 的符号。这对嵌入者来说有点麻烦,因为默认情况下,当将 Node 构建为库时,V8 和 libuv 的符号是隐藏的,原生模块将无法加载,因为它们找不到符号。

因此,为了使原生模块工作,Electron 中暴露了 V8 和 libuv 的符号。对于 V8,这是通过强制暴露 Chromium 配置文件中的所有符号来实现的。对于 libuv,则通过设置 BUILDING_UV_SHARED=1 定义来实现。

在你的应用中启动 Node

在完成 Node 的构建和链接工作后,最后一步是在你的应用中运行 Node。

Node 没有提供太多用于将自身嵌入其他应用的公共 API。通常,你只需调用 node::Startnode::Init 即可启动一个 Node 新实例。然而,如果你正在构建一个基于 Node 的复杂应用,则必须使用 node::CreateEnvironment 等 API 来精确控制每一个步骤。

在 Electron 中,Node 以两种模式启动:一种是在主进程中运行的独立模式,类似于官方 Node 二进制文件;另一种是嵌入模式,将 Node API 插入到网页中。这些细节将在未来的文章中解释。

2016 年 7 月:新应用和聚会

·2 分钟阅读

我们将开始每月一次的总结,重点介绍 Electron 社区的活动。每次总结都会介绍新应用、即将举行的聚会、工具、视频等。


本网站通过社区的 pull request 添加新的 应用聚会。您可以 关注仓库 以获取新添加内容的通知,如果您对网站的 所有 更改不感兴趣,请订阅 博客 RSS feed

如果您开发了 Electron 应用或举办了聚会,请提交一个 pull request 将其添加到网站,它将出现在下一期汇总中。

新应用

Demio一个为入站销售和市场营销构建的网络研讨会平台
Electorrent一个 uTorrent 服务器的远程客户端应用
PhoneGap一个开源框架,让你使用 Web 技术构建出色的移动应用
WordMark一个轻量级的 Markdown 写作者博客发布编辑器
UbAuth一个帮助开发者使用 OAuth 2.0 为 Uber 应用创建访问令牌的应用
HyperTermHTML/JS/CSS 终端
MarpMarkdown 演示文稿编写器
Glyphr Studio一个免费的 Web 字体设计工具,专注于爱好者的字体设计
BitCrypt一个简单的 Windows 文件加密应用:加密你的数据
Trym一款漂亮的 macOS 小应用,帮助你查看、优化和转换 SVG 图标
Booker具有 Markdown 功能的文本编辑器
PhonePresenter最智能的演示文稿点击器
Yout在桌面观看 YouTube 播放列表的新方式

新的聚会

Electron 开源桌面框架伦敦,英国

Electron 内部原理:消息循环集成

·3 分钟阅读

这是解释 Electron 内部原理系列文章的第一篇。本文介绍了 Node 的事件循环如何与 Electron 中的 Chromium 集成。


之前有很多尝试使用 Node 进行 GUI 编程,例如用于 GTK+ 绑定的 node-gui 和用于 QT 绑定的 node-qt。但它们都没有在生产环境中良好工作,因为 GUI 工具包有自己的消息循环,而 Node 使用 libuv 作为自己的事件循环,主线程只能同时运行一个循环。因此,在 Node 中运行 GUI 消息循环的常用技巧是在具有非常小间隔的计时器中“泵取”消息循环,这使得 GUI 界面响应缓慢并占用大量 CPU 资源。

在 Electron 开发过程中,我们也遇到了同样的问题,不过是反过来的:我们必须将 Node 的事件循环集成到 Chromium 的消息循环中。

主进程和渲染进程

在深入探讨消息循环集成的细节之前,我将首先解释 Chromium 的多进程架构。

在 Electron 中有两种类型的进程:主进程和渲染进程(这实际上是非常简化的说法,要查看完整信息,请参阅多进程架构)。主进程负责 GUI 工作,例如创建窗口,而渲染进程只负责运行和渲染网页。

Electron 允许使用 JavaScript 控制主进程和渲染进程,这意味着我们必须将 Node 集成到这两个进程中。

用 libuv 替换 Chromium 的消息循环

我的第一次尝试是使用 libuv 重新实现 Chromium 的消息循环。

对于渲染进程来说这很容易,因为它的消息循环只监听文件描述符和计时器,我只需要用 libuv 实现相应的接口即可。

然而,对于主进程来说要困难得多。每个平台都有自己的 GUI 消息循环类型。macOS Chromium 使用 NSRunLoop,而 Linux 使用 glib。我尝试了许多技巧来从原生 GUI 消息循环中提取底层文件描述符,然后将它们馈送给 libuv 进行迭代,但我仍然遇到了一些不起作用的边缘情况。

所以最后我添加了一个计时器,以很小的间隔轮询 GUI 消息循环。结果是进程持续占用 CPU 资源,并且某些操作有很长的延迟。

在单独的线程中轮询 Node 的事件循环

随着 libuv 的成熟,可以采取另一种方法了。

libuv 引入了 backend fd 的概念,它是一个文件描述符(或句柄),libuv 会为其事件循环轮询它。因此,通过轮询 backend fd,可以在 libuv 中有新事件时收到通知。

所以在 Electron 中,我创建了一个单独的线程来轮询 backend fd,由于我使用的是系统调用进行轮询而不是 libuv API,所以它是线程安全的。每当 libuv 的事件循环中有新事件时,就会向 Chromium 的消息循环发送一条消息,然后 libuv 的事件会在主线程中处理。

通过这种方式,我避免了对 Chromium 和 Node 进行修补,并且主进程和渲染进程使用了相同的代码。

代码

你可以在 electron/atom/common/ 下的 node_bindings 文件中找到消息循环集成的实现。它很容易被希望集成 Node 的项目重用。

更新:实现已移至 electron/shell/common/node_bindings.cc