Skip to content

Commit

Permalink
feat: add exposeInIsolatedWorld(worldId, key, api) to contextBridge
Browse files Browse the repository at this point in the history
  • Loading branch information
akshaydeo committed Jul 19, 2022
1 parent f1746c8 commit ab3f7d3
Show file tree
Hide file tree
Showing 4 changed files with 2,427 additions and 945 deletions.
49 changes: 48 additions & 1 deletion docs/api/context-bridge.md
Expand Up @@ -46,11 +46,17 @@ The `contextBridge` module has the following methods:
* `apiKey` string - The key to inject the API onto `window` with. The API will be accessible on `window[apiKey]`.
* `api` any - Your API, more information on what this API can be and how it works is available below.

### `contextBridge.exposeInIsolatedWorld(worldId, apiKey, api)`

* `worldId` Integer - The ID of the world to inject the API into. This has to be an existing world.
* `apiKey` string - The key to inject the API onto `window` with. The API will be accessible on `window[apiKey]`.
* `api` any - Your API, more information on what this API can be and how it works is available below.

## Usage

### API

The `api` provided to [`exposeInMainWorld`](#contextbridgeexposeinmainworldapikey-api) must be a `Function`, `string`, `number`, `Array`, `boolean`, or an object
The `api` provided to [`exposeInMainWorld`](#contextbridgeexposeinmainworldapikey-api) and [`exposeInIsolatedWorld`](#contextbridgeexposeinisolatedworldworldid-apikey-api) must be a `Function`, `string`, `number`, `Array`, `boolean`, or 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**. Any data / primitives sent in
Expand Down Expand Up @@ -84,6 +90,47 @@ contextBridge.exposeInMainWorld(
)
```

An example of complex API exposed in an isolated world is shown below:

```javascript

const { contextBridge, webFrame } = require('electron')

webFrame.setIsolatedWorldInfo(1005, {
name: 'Isolated World'
})

contextBridge.exposeInIsolatedWorld(
1005,
'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
})
}
}
}
}
)

// To call this API in isolated world, you can use `executeJavaScriptInIsolatedWorld`
webFrame.executeJavaScriptInIsolatedWorld(1005, [
{
code: 'window.electron.doThing()'
}
])
```

### API Functions

`Function` values that you bind through the `contextBridge` are proxied through Electron to ensure that contexts remain isolated. This
Expand Down
27 changes: 23 additions & 4 deletions lib/renderer/api/context-bridge.ts
@@ -1,13 +1,21 @@
const binding = process._linkedBinding('electron_renderer_context_bridge');

const checkContextIsolationEnabled = () => {
if (!process.contextIsolated) throw new Error('contextBridge API can only be used when contextIsolation is enabled');
if (!process.contextIsolated) {
throw new Error(
'contextBridge API can only be used when contextIsolation is enabled'
);
}
};

const contextBridge: Electron.ContextBridge = {
exposeInMainWorld: (key: string, api: any) => {
checkContextIsolationEnabled();
return binding.exposeAPIInMainWorld(key, api);
},
exposeInIsolatedWorld: (worldId: number, key: string, api: any) => {
checkContextIsolationEnabled();
return binding.exposeAPIInIsolatedWorld(worldId, key, api);
}
};

Expand All @@ -18,11 +26,22 @@ export const internalContextBridge = {
overrideGlobalValueFromIsolatedWorld: (keys: string[], value: any) => {
return binding._overrideGlobalValueFromIsolatedWorld(keys, value, false);
},
overrideGlobalValueWithDynamicPropsFromIsolatedWorld: (keys: string[], value: any) => {
overrideGlobalValueWithDynamicPropsFromIsolatedWorld: (
keys: string[],
value: any
) => {
return binding._overrideGlobalValueFromIsolatedWorld(keys, value, true);
},
overrideGlobalPropertyFromIsolatedWorld: (keys: string[], getter: Function, setter?: Function) => {
return binding._overrideGlobalPropertyFromIsolatedWorld(keys, getter, setter || null);
overrideGlobalPropertyFromIsolatedWorld: (
keys: string[],
getter: Function,
setter?: Function
) => {
return binding._overrideGlobalPropertyFromIsolatedWorld(
keys,
getter,
setter || null
);
},
isInMainWorld: () => binding._isCalledFromMainWorld() as boolean
};
Expand Down
55 changes: 55 additions & 0 deletions shell/renderer/api/electron_api_context_bridge.cc
Expand Up @@ -607,6 +607,59 @@ void ExposeAPIInMainWorld(v8::Isolate* isolate,
}
}

void ExposeAPIInIsolatedWorld(v8::Isolate* isolate,
const int world_id,
const std::string& key,
v8::Local<v8::Value> api,
gin_helper::Arguments* args) {
TRACE_EVENT1("electron", "ContextBridge::ExposeAPIInIsolatedWorld", "worldId",
world_id);

auto* render_frame = GetRenderFrame(isolate->GetCurrentContext()->Global());
CHECK(render_frame);
auto* frame = render_frame->GetWebFrame();
CHECK(frame);

v8::Local<v8::Context> main_context = frame->MainWorldScriptContext();
v8::Local<v8::Context> isolated_context =
frame->GetScriptContextFromWorldId(isolate, world_id);

gin_helper::Dictionary isolated(isolated_context->GetIsolate(),
isolated_context->Global());

if (isolated.Has(key)) {
args->ThrowError(
"Cannot bind an API on top of an existing property on the window "
"object");
return;
}

v8::Local<v8::Context> isolated_context2 = frame->GetScriptContextFromWorldId(
args->isolate(), WorldIDs::ISOLATED_WORLD_ID);

{
context_bridge::ObjectCache object_cache;
v8::Context::Scope main_context_scope(main_context);

v8::MaybeLocal<v8::Value> maybe_proxy = PassValueToOtherContext(
isolated_context2, isolated_context, api, &object_cache, false, 0);
if (maybe_proxy.IsEmpty())
return;
auto proxy = maybe_proxy.ToLocalChecked();

if (base::FeatureList::IsEnabled(features::kContextBridgeMutability)) {
isolated.Set(key, proxy);
return;
}

if (proxy->IsObject() && !proxy->IsTypedArray() &&
!DeepFreeze(proxy.As<v8::Object>(), isolated_context2))
return;

isolated.SetReadOnlyNonConfigurable(key, proxy);
}
}

gin_helper::Dictionary TraceKeyPath(const gin_helper::Dictionary& start,
const std::vector<std::string>& key_path) {
gin_helper::Dictionary current = start;
Expand Down Expand Up @@ -717,6 +770,8 @@ void Initialize(v8::Local<v8::Object> exports,
v8::Isolate* isolate = context->GetIsolate();
gin_helper::Dictionary dict(isolate, exports);
dict.SetMethod("exposeAPIInMainWorld", &electron::api::ExposeAPIInMainWorld);
dict.SetMethod("exposeAPIInIsolatedWorld",
&electron::api::ExposeAPIInIsolatedWorld);
dict.SetMethod("_overrideGlobalValueFromIsolatedWorld",
&electron::api::OverrideGlobalValueFromIsolatedWorld);
dict.SetMethod("_overrideGlobalPropertyFromIsolatedWorld",
Expand Down

0 comments on commit ab3f7d3

Please sign in to comment.