-
Notifications
You must be signed in to change notification settings - Fork 15.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add a new contextBridge module (#20307)
* feat: add a new contextBridge module * chore: fix docs linting * feat: add support for function arguments being proxied * chore: ensure that contextBridge can only be used when contextIsolation is enabled * docs: getReverseBinding can be null * docs: fix broken links in md file * feat: add support for promises in function parameters * fix: linting failure for explicit constructor * Update atom_api_context_bridge.cc * chore: update docs and API design as per feedback * refactor: remove reverse bindings and handle GC'able functions across the bridge * chore: only expose debugGC in testing builds * fix: do not proxy promises as objects * spec: add complete spec coverage for contextBridge * spec: add tests for null/undefined and the anti-overwrite logic * chore: fix linting * spec: add complex nested back-and-forth function calling * fix: expose contextBridge in sandboxed renderers * refactor: improve security of default_app using the new contextBridge module * s/bindAPIInMainWorld/exposeInMainWorld * chore: sorry for this commit, its a big one, I fixed like everything and refactored a lot * chore: remove PassedValueCache as it is unused now Values transferred from context A to context B are now cachde in the RenderFramePersistenceStore * chore: move to anonymous namespace * refactor: remove PassValueToOtherContextWithCache * chore: remove commented unused code blocks * chore: remove .only * chore: remote commented code * refactor: extract RenderFramePersistenceStore * spec: ensure it works with numbered keys * fix: handle number keys correctly * fix: sort out the linter * spec: update default_app asar spec for removed file * refactor: change signatures to return v8 objects directly rather than the mate dictionary handle * refactor: use the v8 serializer to support cloneable buffers and other object types * chore: fix linting * fix: handle hash collisions with a linked list in the map * fix: enforce a recursion limit on the context bridge * chore: fix linting * chore: remove TODO * chore: adapt for PR feedback * chore: remove .only * chore: clean up docs and clean up the proxy map when objects are released * chore: ensure we cache object values that are cloned through the V8 serializer
- Loading branch information
1 parent
8099e61
commit 0090616
Showing
21 changed files
with
1,680 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,111 @@ | |||
# contextBridge | |||
|
|||
> Create a safe, bi-directional, synchronous bridge across isolated contexts | |||
Process: [Renderer](../glossary.md#renderer-process) | |||
|
|||
An example of exposing an API to a renderer from an isolated preload script is given below: | |||
|
|||
```javascript | |||
// Preload (Isolated World) | |||
const { contextBridge, ipcRenderer } = require('electron') | |||
|
|||
contextBridge.exposeInMainWorld( | |||
'electron', | |||
{ | |||
doThing: () => ipcRenderer.send('do-a-thing') | |||
} | |||
) | |||
``` | |||
|
|||
```javascript | |||
// Renderer (Main World) | |||
|
|||
window.electron.doThing() | |||
``` | |||
|
|||
## Glossary | |||
|
|||
### Main World | |||
|
|||
The "Main World" is the javascript context that your main renderer code runs in. By default the page you load in your renderer | |||
executes code in this world. | |||
|
|||
### Isolated World | |||
|
|||
When `contextIsolation` is enabled in your `webPreferences` your `preload` scripts run in an "Isolated World". You can read more about | |||
context isolation and what it affects in the [BrowserWindow](browser-window.md) docs. | |||
|
|||
## Methods | |||
|
|||
The `contextBridge` module has the following methods: | |||
|
|||
### `contextBridge.exposeInMainWorld(apiKey, api)` | |||
|
|||
* `apiKey` String - The key to inject the API onto `window` with. The API will be accessible on `window[apiKey]`. | |||
* `api` Record<String, any> - Your API object, more information on what this API can be and how it works is available below. | |||
|
|||
## Usage | |||
|
|||
### API Objects | |||
|
|||
The `api` object provided to [`exposeInMainWorld`](#contextbridgeexposeinmainworldapikey-api) must be an object | |||
whose keys are strings and values are a `Function`, `String`, `Number`, `Array`, `Boolean` or another nested object that meets the same conditions. | |||
|
|||
`Function` values are proxied to the other context and all other values are **copied** and **frozen**. I.e. Any data / primitives sent in | |||
the API object become immutable and updates on either side of the bridge do not result in an update on the other side. | |||
|
|||
An example of a complex API object is shown below. | |||
|
|||
```javascript | |||
const { contextBridge } = require('electron') | |||
|
|||
contextBridge.exposeInMainWorld( | |||
'electron', | |||
{ | |||
doThing: () => ipcRenderer.send('do-a-thing'), | |||
myPromises: [Promise.resolve(), Promise.reject(new Error('whoops'))], | |||
anAsyncFunction: async () => 123, | |||
data: { | |||
myFlags: ['a', 'b', 'c'], | |||
bootTime: 1234 | |||
}, | |||
nestedAPI: { | |||
evenDeeper: { | |||
youCanDoThisAsMuchAsYouWant: { | |||
fn: () => ({ | |||
returnData: 123 | |||
}) | |||
} | |||
} | |||
} | |||
} | |||
) | |||
``` | |||
|
|||
### API Functions | |||
|
|||
`Function` values that you bind through the `contextBridge` are proxied through Electron to ensure that contexts remain isolated. This | |||
results in some key limitations that we've outlined below. | |||
|
|||
#### Parameter / Error / Return Type support | |||
|
|||
Because parameters, errors and return values are **copied** when they are sent over the bridge there are only certain types that can be used. | |||
At a high level if the type you want to use can be serialized and un-serialized into the same object it will work. A table of type support | |||
has been included below for completeness. | |||
|
|||
| Type | Complexity | Parameter Support | Return Value Support | Limitations | | |||
| ---- | ---------- | ----------------- | -------------------- | ----------- | | |||
| `String` | Simple | ✅ | ✅ | N/A | | |||
| `Number` | Simple | ✅ | ✅ | N/A | | |||
| `Boolean` | Simple | ✅ | ✅ | N/A | | |||
| `Object` | Complex | ✅ | ✅ | Keys must be supported "Simple" types in this table. Values must be supported in this table. Prototype modifications are dropped. Sending custom classes will copy values but not the prototype. | | |||
| `Array` | Complex | ✅ | ✅ | Same limitations as the `Object` type | | |||
| `Error` | Complex | ✅ | ✅ | Errors that are thrown are also copied, this can result in the message and stack trace of the error changing slightly due to being thrown in a different context | | |||
| `Promise` | Complex | ✅ | ✅ | Promises are only proxied if they are a the return value or exact parameter. Promises nested in arrays or obejcts will be dropped. | | |||
| `Function` | Complex | ✅ | ✅ | Prototype modifications are dropped. Sending classes or constructors will not work. | | |||
| [Cloneable Types](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) | Simple | ✅ | ✅ | See the linked document on cloneable types | | |||
| `Symbol` | N/A | ❌ | ❌ | Symbols cannot be copied across contexts so they are dropped | | |||
|
|||
|
|||
If the type you care about is not in the above table it is probably not supported. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Original file line | Diff line number | Diff line change |
---|---|---|---|
@@ -0,0 +1,20 @@ | |||
const { hasSwitch } = process.electronBinding('command_line') | |||
const binding = process.electronBinding('context_bridge') | |||
|
|||
const contextIsolationEnabled = hasSwitch('context-isolation') | |||
|
|||
const checkContextIsolationEnabled = () => { | |||
if (!contextIsolationEnabled) throw new Error('contextBridge API can only be used when contextIsolation is enabled') | |||
} | |||
|
|||
const contextBridge = { | |||
exposeInMainWorld: (key: string, api: Record<string, any>) => { | |||
checkContextIsolationEnabled() | |||
return binding.exposeAPIInMainWorld(key, api) | |||
}, | |||
debugGC: () => binding._debugGCMaps({}) | |||
} | |||
|
|||
if (!binding._debugGCMaps) delete contextBridge.debugGC | |||
|
|||
export default contextBridge |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.