Skip to main content

上下文隔离

¥Context Isolation

它是什么?

¥What is it?

上下文隔离是一项功能,可确保你的 preload 脚本和 Electron 的内部逻辑在与你在 webContents 中加载的网站不同的上下文中运行。这对于安全目的很重要,因为它有助于防止网站访问 Electron 内部结构或预加载脚本有权访问的强大 API。

¥Context Isolation is a feature that ensures that both your preload scripts and Electron's internal logic run in a separate context to the website you load in a webContents. This is important for security purposes as it helps prevent the website from accessing Electron internals or the powerful APIs your preload script has access to.

这意味着你的预加载脚本有权访问的 window 对象实际上与网站有权访问的对象不同。例如,如果你在预加载脚本中设置了 window.hello = 'wave' 并启用了上下文隔离,则当网站尝试访问 window.hello 时,window.hello 将是未定义的。

¥This means that the window object that your preload script has access to is actually a different object than the website would have access to. For example, if you set window.hello = 'wave' in your preload script and context isolation is enabled, window.hello will be undefined if the website tries to access it.

自 Electron 12 起,上下文隔离已默认启用,并且它是所有应用的推荐安全设置。

¥Context isolation has been enabled by default since Electron 12, and it is a recommended security setting for all applications.

迁移

¥Migration

在没有上下文隔离的情况下,我曾经使用 window.X = apiObject 从预加载脚本中提供 API。怎么办?

¥Without context isolation, I used to provide APIs from my preload script using window.X = apiObject. Now what?

前:上下文隔离已禁用

¥Before: context isolation disabled

将预加载脚本中的 API 公开到渲染器进程中加载的网站是一种常见的用例。禁用上下文隔离后,你的预加载脚本将与渲染器共享一个公共的全局 window 对象。然后,你可以将任意属性附加到预加载脚本:

¥Exposing APIs from your preload script to a loaded website in the renderer process is a common use-case. With context isolation disabled, your preload script would share a common global window object with the renderer. You could then attach arbitrary properties to a preload script:

preload.js
// preload with contextIsolation disabled
window.myAPI = {
doAThing: () => {}
}

然后可以在渲染器进程中直接使用 doAThing() 函数:

¥The doAThing() function could then be used directly in the renderer process:

renderer.js
// use the exposed API in the renderer
window.myAPI.doAThing()

后:启用上下文隔离

¥After: context isolation enabled

Electron 有一个专用模块可以帮助你轻松地完成此操作。contextBridge 模块可用于安全地将 API 从预加载脚本的隔离上下文公开到网站运行的上下文。该 API 也可以像以前一样从 window.myAPI 上的网站访问。

¥There is a dedicated module in Electron to help you do this in a painless way. The contextBridge module can be used to safely expose APIs from your preload script's isolated context to the context the website is running in. The API will also be accessible from the website on window.myAPI just like it was before.

preload.js
// preload with contextIsolation enabled
const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('myAPI', {
doAThing: () => {}
})
renderer.js
// use the exposed API in the renderer
window.myAPI.doAThing()

请阅读上面链接的 contextBridge 文档以充分了解其局限性。例如,你无法通过桥发送自定义原型或符号。

¥Please read the contextBridge documentation linked above to fully understand its limitations. For instance, you can't send custom prototypes or symbols over the bridge.

安全考虑

¥Security considerations

仅启用 contextIsolation 并使用 contextBridge 并不自动意味着你所做的一切都是安全的。例如,这段代码是不安全的。

¥Just enabling contextIsolation and using contextBridge does not automatically mean that everything you do is safe. For instance, this code is unsafe.

preload.js
// ❌ Bad code
contextBridge.exposeInMainWorld('myAPI', {
send: ipcRenderer.send
})

它直接公开强大的 API,无需任何类型的参数过滤。这将允许任何网站发送任意 IPC 消息,而你不希望这种情况发生。公开基于 IPC 的 API 的正确方法是为每个 IPC 消息提供一种方法。

¥It directly exposes a powerful API without any kind of argument filtering. This would allow any website to send arbitrary IPC messages, which you do not want to be possible. The correct way to expose IPC-based APIs would instead be to provide one method per IPC message.

preload.js
// ✅ Good code
contextBridge.exposeInMainWorld('myAPI', {
loadPreferences: () => ipcRenderer.invoke('load-prefs')
})

与 TypeScript 一起使用

¥Usage with TypeScript

如果你使用 TypeScript 构建 Electron 应用,你将需要向通过上下文桥公开的 API 添加类型。除非你使用 声明文件 扩展类型,否则渲染器的 window 对象不会具有正确的类型。

¥If you're building your Electron app with TypeScript, you'll want to add types to your APIs exposed over the context bridge. The renderer's window object won't have the correct typings unless you extend the types with a declaration file.

例如,给定这个 preload.ts 脚本:

¥For example, given this preload.ts script:

preload.ts
contextBridge.exposeInMainWorld('electronAPI', {
loadPreferences: () => ipcRenderer.invoke('load-prefs')
})

你可以创建 interface.d.ts 声明文件并全局扩展 Window 接口:

¥You can create a interface.d.ts declaration file and globally augment the Window interface:

interface.d.ts
export interface IElectronAPI {
loadPreferences: () => Promise<void>,
}

declare global {
interface Window {
electronAPI: IElectronAPI
}
}

这样做将确保 TypeScript 编译器在渲染器进程中编写脚本时了解全局 window 对象的 electronAPI 属性:

¥Doing so will ensure that the TypeScript compiler will know about the electronAPI property on your global window object when writing scripts in your renderer process:

renderer.ts
window.electronAPI.loadPreferences()