Skip to main content

Context Isolation上下文隔离

What is it?这是怎么一回事?

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. 上下文隔离是一项功能,可确保您的preload脚本和Electron的内部逻辑在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.这对于安全目的很重要,因为它有助于防止网站访问Electron内部或预加载脚本可以访问的强大API。

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. 这意味着预加载脚本可以访问的window对象实际上与网站可以访问的对象不同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.例如,如果您在预加载脚本中设置window.hello = 'wave',并且启用了上下文隔离,则如果网站试图访问window.hello,则window.hello将未定义。

Context isolation has been enabled by default since Electron 12, and it is a recommended security setting for all applications.自Electron 12以来,默认情况下启用了上下文隔离,这是所有应用程序的推荐安全设置。

Migration迁移

Without context isolation, I used to provide APIs from my preload script using window.X = apiObject. 在没有上下文隔离的情况下,我使用window.X=apiObject从预加载脚本中提供API。Now what?现在呢?

Before: context isolation disabled之前:禁用上下文隔离

Exposing APIs from your preload script to a loaded website in the renderer process is a common use-case. 在渲染器过程中,将预加载脚本中的API公开到加载的网站是一个常见的用例。With context isolation disabled, your preload script would share a common global window object with the renderer. 禁用上下文隔离后,预加载脚本将与渲染器共享一个公共全局window对象。You could then attach arbitrary properties to a preload script:然后可以将任意属性附加到预加载脚本:

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

The doAThing() function could then be used directly in the renderer process:然后,doAThing()函数可以直接在渲染器进程中使用:

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

After: context isolation enabled之后:启用上下文隔离

There is a dedicated module in Electron to help you do this in a painless way. Electron中有一个专门的模块,可以帮助您以一种无痛的方式完成这项工作。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. contextBridge模块可用于安全地将API从预加载脚本的隔离上下文公开到网站运行的上下文。The API will also be accessible from the website on window.myAPI just like it was before.该API也可以从window.myAPI上的网站访问,就像以前一样。

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()

Please read the contextBridge documentation linked above to fully understand its limitations. 请阅读上面链接的contextBridge文档,以充分了解其局限性。For instance, you can't send custom prototypes or symbols over the bridge.例如,您不能通过桥发送自定义原型或符号。

Security considerations安全考虑

Just enabling contextIsolation and using contextBridge does not automatically mean that everything you do is safe. 仅仅启用contextIsolation和使用contextBridge并不意味着您所做的一切都是安全的。For instance, this code is unsafe.例如,此代码不安全

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

It directly exposes a powerful API without any kind of argument filtering. 它直接公开了一个强大的API,而不需要任何类型的参数筛选。This would allow any website to send arbitrary IPC messages, which you do not want to be possible. 这将允许任何网站发送任意IPC消息,这是您不希望的。The correct way to expose IPC-based APIs would instead be to provide one method per IPC message.公开基于IPC的API的正确方法是为每个IPC消息提供一个方法。

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

Usage with TypeScript与TypeScript一起使用

If you're building your Electron app with TypeScript, you'll want to add types to your APIs exposed over the context bridge. 如果您正在使用TypeScript构建您的Electron应用程序,那么您需要将类型添加到通过上下文桥公开的API中。The renderer's window object won't have the correct typings unless you extend the types with a declaration file.除非使用声明文件扩展类型,否则渲染器的window对象将没有正确的类型。

For example, given this preload.ts script:例如,给定此preload.ts脚本:

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

You can create a renderer.d.ts declaration file and globally augment the Window interface:您可以创建一个renderer.d.ts声明文件并全局扩展Window界面:

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

declare global {
interface Window {
electronAPI: IElectronAPI
}
}

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:这样做将确保TypeScript编译器在渲染器进程中编写脚本时了解全局window对象上的electronAPI属性:

renderer.ts
window.electronAPI.loadPreferences()