feat: contextBridge.executeInMainWorld (#45330)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Samuel Maddock <samuel.maddock@gmail.com>
This commit is contained in:
parent
9d696ceffe
commit
fa03b92f7e
8 changed files with 528 additions and 130 deletions
|
@ -108,7 +108,7 @@ describe('contextBridge', () => {
|
|||
};
|
||||
|
||||
const callWithBindings = (fn: Function, worldId: number = 0) =>
|
||||
worldId === 0 ? w.webContents.executeJavaScript(`(${fn.toString()})(window)`) : w.webContents.executeJavaScriptInIsolatedWorld(worldId, [{ code: `(${fn.toString()})(window)` }]); ;
|
||||
worldId === 0 ? w.webContents.executeJavaScript(`(${fn.toString()})(window)`) : w.webContents.executeJavaScriptInIsolatedWorld(worldId, [{ code: `(${fn.toString()})(window)` }]);
|
||||
|
||||
const getGCInfo = async (): Promise<{
|
||||
trackedValues: number;
|
||||
|
@ -408,6 +408,17 @@ describe('contextBridge', () => {
|
|||
expect(result).equal(true);
|
||||
});
|
||||
|
||||
it('should proxy function arguments only once', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', (a: any, b: any) => a === b);
|
||||
});
|
||||
const result = await callWithBindings(async (root: any) => {
|
||||
const obj = { foo: 1 };
|
||||
return root.example(obj, obj);
|
||||
});
|
||||
expect(result).to.be.true();
|
||||
});
|
||||
|
||||
it('should properly handle errors thrown in proxied functions', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', () => { throw new Error('oh no'); });
|
||||
|
@ -1290,6 +1301,131 @@ describe('contextBridge', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('executeInMainWorld', () => {
|
||||
it('serializes function and proxies args', async () => {
|
||||
await makeBindingWindow(async () => {
|
||||
const values = [
|
||||
undefined,
|
||||
null,
|
||||
123,
|
||||
'string',
|
||||
true,
|
||||
[123, 'string', true, ['foo']],
|
||||
() => 'string',
|
||||
Symbol('foo')
|
||||
];
|
||||
function appendArg (arg: any) {
|
||||
// @ts-ignore
|
||||
globalThis.args = globalThis.args || [];
|
||||
// @ts-ignore
|
||||
globalThis.args.push(arg);
|
||||
}
|
||||
for (const value of values) {
|
||||
try {
|
||||
await contextBridge.executeInMainWorld({
|
||||
func: appendArg,
|
||||
args: [value]
|
||||
});
|
||||
} catch {
|
||||
contextBridge.executeInMainWorld({
|
||||
func: appendArg,
|
||||
args: ['FAIL']
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
const result = await callWithBindings(() => {
|
||||
// @ts-ignore
|
||||
return globalThis.args.map(arg => {
|
||||
// Map unserializable IPC types to their type string
|
||||
if (['function', 'symbol'].includes(typeof arg)) {
|
||||
return typeof arg;
|
||||
} else {
|
||||
return arg;
|
||||
}
|
||||
});
|
||||
});
|
||||
expect(result).to.deep.equal([
|
||||
undefined,
|
||||
null,
|
||||
123,
|
||||
'string',
|
||||
true,
|
||||
[123, 'string', true, ['foo']],
|
||||
'function',
|
||||
'symbol'
|
||||
]);
|
||||
});
|
||||
|
||||
it('allows function args to be invoked', async () => {
|
||||
const donePromise = once(ipcMain, 'done');
|
||||
makeBindingWindow(() => {
|
||||
const uuid = crypto.randomUUID();
|
||||
const done = (receivedUuid: string) => {
|
||||
if (receivedUuid === uuid) {
|
||||
require('electron').ipcRenderer.send('done');
|
||||
}
|
||||
};
|
||||
contextBridge.executeInMainWorld({
|
||||
func: (callback, innerUuid) => {
|
||||
callback(innerUuid);
|
||||
},
|
||||
args: [done, uuid]
|
||||
});
|
||||
});
|
||||
await donePromise;
|
||||
});
|
||||
|
||||
it('proxies arguments only once', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
const obj = {};
|
||||
// @ts-ignore
|
||||
globalThis.result = contextBridge.executeInMainWorld({
|
||||
func: (a, b) => a === b,
|
||||
args: [obj, obj]
|
||||
});
|
||||
});
|
||||
const result = await callWithBindings(() => {
|
||||
// @ts-ignore
|
||||
return globalThis.result;
|
||||
}, 999);
|
||||
expect(result).to.be.true();
|
||||
});
|
||||
|
||||
it('safely clones returned objects', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
const obj = contextBridge.executeInMainWorld({
|
||||
func: () => ({})
|
||||
});
|
||||
// @ts-ignore
|
||||
globalThis.safe = obj.constructor === Object;
|
||||
});
|
||||
const result = await callWithBindings(() => {
|
||||
// @ts-ignore
|
||||
return globalThis.safe;
|
||||
}, 999);
|
||||
expect(result).to.be.true();
|
||||
});
|
||||
|
||||
it('uses internal Function.prototype.toString', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
const funcHack = () => {
|
||||
// @ts-ignore
|
||||
globalThis.hacked = 'nope';
|
||||
};
|
||||
funcHack.toString = () => '() => { globalThis.hacked = \'gotem\'; }';
|
||||
contextBridge.executeInMainWorld({
|
||||
func: funcHack
|
||||
});
|
||||
});
|
||||
const result = await callWithBindings(() => {
|
||||
// @ts-ignore
|
||||
return globalThis.hacked;
|
||||
});
|
||||
expect(result).to.equal('nope');
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue