Electron Extensions API 详解:加载、管理与监听 Chrome 扩展的完整指南
【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron
本文围绕 Electron 的Extensions类展开,介绍如何通过session.extensions在 Electron 应用中加载未打包(unpacked)的 Chrome 扩展,管理其生命周期,并监听extension-loaded、extension-ready、extension-unloaded等实例事件;同时结合 Electron 源码中electron_api_extensions.cc与electron_extension_system.cc的实现,剖析路径校验、临时会话限制、allowFileAccess选项和加载警告等底层机制,帮助你写出可靠的扩展集成代码。
获取 Extensions 实例:Session的extensions属性
Extensions类**不会从'electron' 模块直接导出**。它只能作为其他 API 方法的返回值使用,具体获取方式是访问Session实例的extensions` 属性:
const { session } = require('electron') const extensions = session.defaultSession.extensions该属性在 Session 文档 中声明为只读(ses.extensionsReadonly),每个Session对应一个独立的扩展实例。因此扩展是按 session 安装的:使用session.fromPartition('persist:...')创建的持久化 session 加载的扩展,只属于该 session,且不同 session 之间互不可见。
注意:旧版 API
ses.loadExtension/ses.removeExtension/ses.getExtension/ses.getAllExtensions已被标记为弃用(Deprecated),官方推荐使用新的ses.extensions.loadExtension等 API(见 session.md)。
实例事件
Extensions实例是一个事件发射器,共提供三个实例事件。从源码 electron_api_extensions.h 可以看到,C++ 侧的Extensions类实现了extensions::ExtensionRegistryObserver接口,三个 JS 事件分别是 ChromiumExtensionRegistry观察者回调OnExtensionLoaded、OnExtensionReady、OnExtensionUnloaded的直通映射(见 electron_api_extensions.cc):
void Extensions::OnExtensionLoaded(content::BrowserContext* browser_context, const extensions::Extension* extension) { Emit("extension-loaded", extension); } void Extensions::OnExtensionUnloaded(content::BrowserContext* browser_context, const extensions::Extension* extension, extensions::UnloadedExtensionReason reason) { Emit("extension-unloaded", extension); } void Extensions::OnExtensionReady(content::BrowserContext* browser_context, const extensions::Extension* extension) { Emit("extension-ready", extension); }事件:extension-loaded
返回值:
eventEventextensionExtension
扩展被加载后发出。每当一个扩展被加入该 session 的“enabled”扩展集合时就会触发,包括:
- 通过
extensions.loadExtension加载的扩展; - 扩展被重新加载的场景:
- 从崩溃中恢复;
- 扩展自身请求重载(调用
chrome.runtime.reload())。
事件:extension-unloaded
返回值:
eventEventextensionExtension
扩展被卸载后发出。当调用extensions.removeExtension时触发。从源码 electron_extension_system.cc 可以看到,RemoveExtension底层是调用UnloadExtension(extension_id, UnloadedExtensionReason::UNINSTALL),即以“卸载”原因通知扩展系统移除该扩展,进而触发OnExtensionUnloaded回调。
事件:extension-ready
返回值:
eventEventextensionExtension
扩展已加载、且所有必要的浏览器状态都已初始化以支持其 background page 启动后发出。也就是说,extension-loaded表示扩展元数据注册完成,而extension-ready表示它已可完整运行(例如可以开始执行 background 逻辑)。在 spec/extensions-spec.ts 的测试中可以看到典型的“加载 + ready”监听写法:
const loadedPromise = once(customSession.extensions, 'extension-loaded') // ... 并监听 'extension-ready'事件与加载 Promise 配合使用,可以精确判断扩展可用的时机,再执行依赖扩展的行为。
实例方法
Extensions实例提供四个实例方法,均在 C++ 侧通过gin::ObjectTemplateBuilder注册(见 electron_api_extensions.cc):
.SetMethod("loadExtension", &Extensions::LoadExtension) .SetMethod("removeExtension", &Extensions::RemoveExtension) .SetMethod("getExtension", &Extensions::GetExtension) .SetMethod("getAllExtensions", &Extensions::GetAllExtensions)extensions.loadExtension(path[, options])
pathstring - 包含未打包 Chrome 扩展的目录路径optionsObject(可选)allowFileAccessboolean - 是否允许扩展通过file://协议读取本地文件,并将 content script 注入到file://页面。例如在file://URL 上加载 DevTools 扩展时必须开启此选项。默认值为false。
返回Promise<Extension>- 扩展加载完成后 resolve。
该方法在扩展无法加载时会抛出异常(Promise reject)。如果扩展安装时存在警告(例如扩展请求了 Electron 不支持的某个 API),警告会输出到控制台——从源码看,这些警告以ExtensionLoadWarning的类别名发出(electron_api_extensions.cc):
if (!error_msg.empty()) util::EmitWarning(promise.isolate(), error_msg, "ExtensionLoadWarning"); promise.Resolve(extension)这一点在测试中也有直接验证:加载带有格式错误的host_permissions的扩展时,扩展仍会加载成功,但会收到警告(见 spec/extensions-spec.ts):
await expectWarningMessages( async () => { const extPath = path.join(fixtures, 'extensions', 'host-permissions', 'malformed') await customSession.extensions.loadExtension(extPath) }, { name: 'ExtensionLoadWarning', message: /URL pattern 'malformed_host' is malformed/ } )Electron 不支持完整的 Chrome 扩展 API 范围,仅支持一个子集(主要用于 DevTools 扩展和 Chromium 内部扩展),支持的 manifest 键与chrome.*API 明细见 Chrome Extension Support。
另外注意一个历史行为变更:在较早版本的 Electron 中,加载过的扩展会在后续应用启动时自动保留;现在不再如此——如果希望扩展被加载,必须在应用每次启动时都调用loadExtension。
典型用法(加载 React DevTools 扩展):
const { app, session } = require('electron') const path = require('node:path') app.whenReady().then(async () => { await session.defaultSession.extensions.loadExtension( path.join(__dirname, 'react-devtools'), // allowFileAccess is required to load the DevTools extension on file:// URLs. { allowFileAccess: true } ) // Note that in order to use the React DevTools extension, you'll need to // download and unzip a copy of the extension. })该 API不支持加载已打包(.crx)的扩展,只能加载 unpacked 目录。
约束条件与底层实现
loadExtension有以下硬性约束,均可在 C++ 实现 LoadExtension 中找到对应代码:
必须在
app的ready事件之后调用。路径必须是绝对路径。源码中显式校验并拒绝相对路径:
if (!extension_path.IsAbsolute()) { promise.RejectWithErrorMessage( "The path to the extension in 'loadExtension' must be absolute"); return handle; }这就是为什么示例中用
path.join(__dirname, 'react-devtools')拼出绝对路径,而不是直接传'react-devtools'。不能在内存(非持久化)session 中加载。源码检查
IsOffTheRecord(),拒绝时抛出Extensions cannot be loaded in a temporary session(electron_api_extensions.cc)。测试用例 spec/extensions-spec.ts 验证了这一行为:it('loading an extension in a temporary session throws an error', async () => { const customSession = session.fromPartition(require('uuid').v4()) await expect( customSession.extensions.loadExtension(path.join(fixtures, 'extensions', 'content-script-test')) ).to.eventually.be.rejectedWith('Extensions cannot be loaded in a temporary session') })也就是说,只有
defaultSession或带persist:前缀的 partition 才能加载扩展;session.fromPartition('uuid')这类临时 session 会直接抛错。allowFileAccess的底层含义。源码中该选项会被映射为 Chromium 扩展加载标志(electron_api_extensions.cc):int load_flags = extensions::Extension::FOLLOW_SYMLINKS_ANYWHERE; gin_helper::Dictionary options; if (args->GetNext(&options)) { bool allowFileAccess = false; options.Get("allowFileAccess", &allowFileAccess); if (allowFileAccess) load_flags |= extensions::Extension::ALLOW_FILE_ACCESS; }即不开启时,扩展默认只能作用于
http://、https://等网络协议页面(file://页面无法被 content script 注入),而开启ALLOW_FILE_ACCESS后扩展才被允许访问file://资源。这正是 DevTools 类扩展在本地file://页面上工作时必须传{ allowFileAccess: true }的原因。
extensions.removeExtension(extensionId)
extensionIdstring - 要移除的扩展 ID
卸载指定扩展。该 API 同样不能在app的ready事件之前调用。在 spec/extensions-spec.ts 中可以看到测试清理时的标准用法:遍历getAllExtensions()并逐一removeExtension,确保测试之间互不污染:
afterEach(() => { for (const e of session.defaultSession.extensions.getAllExtensions()) { session.defaultSession.extensions.removeExtension(e.id) } })extensions.getExtension(extensionId)
extensionIdstring - 要查询的扩展 ID
返回Extension | null- 给定 ID 的已加载扩展。源码实现直接查询该 browser context 的ExtensionRegistry,未找到时返回null(electron_api_extensions.cc)。该 API 不能在app的ready事件之前调用。
extensions.getAllExtensions()
返回Extension[]- 所有已加载扩展的列表。
一个值得注意的细节:实现中会过滤掉 Chromium 的 component 扩展(如内置的 PDF 查看器),只返回由用户通过loadExtension加载的扩展(electron_api_extensions.cc):
for (const auto& extension : extensions) { if (extension->location() != extensions::mojom::ManifestLocation::kComponent) extensions_vector.emplace_back(extension.get()); }该 API 也不能在app的ready事件之前调用。
返回的 Extension 对象结构
loadExtensionresolve 以及各事件回传的都是 Extension 对象,包含字段:
idstring - 扩展 ID(chrome.runtime.id对应的值,可用于后续removeExtension/getExtension)manifestany - 扩展 manifest 数据的一份拷贝namestringpathstring - 扩展的文件路径versionstringurlstring - 扩展的chrome-extension://URL
拿到id后即可与getExtension/removeExtension配合做完整的加载—查询—卸载生命周期管理。
支持的扩展 API 范围(速览)
由于loadExtension文档明确指向了支持范围说明,这里给出要点(完整清单见 docs/api/extensions.md):
- 完整支持:
chrome.devtools.inspectedWindow、chrome.devtools.network、chrome.devtools.panels、chrome.scripting、chrome.webRequest(注意 Electron 自身的webRequest模块在冲突时优先于chrome.webRequest)。 - 部分支持:
chrome.runtime(支持lastError、id属性及getBackgroundPage、getManifest、getPlatformInfo、getURL、connect、sendMessage、reload方法与onStartup、onInstalled、onSuspend、onSuspendCanceled、onConnect、onMessage事件);chrome.tabs(支持sendMessage、reload、executeScript,query与update为部分支持;且-1不代表“当前活动标签”);chrome.storage(仅local,不支持sync/managed);chrome.management(getAll、get、getSelf、getPermissionWarningsById、getPermissionWarningsByManifest与onEnabled/onDisabled);chrome.extension(仅lastError、getURL、getBackgroundPage)。 - 支持的 manifest 键:
name、version、author、permissions、content_scripts、default_locale、devtools_page、short_name、host_permissions(Manifest V3)、manifest_version、background(Manifest V2)、minimum_chrome_version。
列表之外的 API 即使当前碰巧可用,其支持也是临时的,随时可能移除。
小结
Extensions实例只能通过session.extensions获取,扩展按 session 隔离,且每次应用启动都必须重新loadExtension。loadExtension要求绝对路径、仅支持 unpacked 扩展、仅限持久化 session;allowFileAccess: true是扩展作用于file://页面(如 DevTools 扩展)的必要开关。- 加载失败 reject、加载警告走
ExtensionLoadWarning、临时会话抛Extensions cannot be loaded in a temporary session——这些行为均有 spec/extensions-spec.ts 中的测试用例佐证,可用于回归验证。 - 通过
extension-loaded/extension-ready/extension-unloaded三个事件,可以精确掌握扩展从注册、就绪到卸载的完整生命周期。
【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考