From 10f3e4dccc9ec6f25b6e7b60d459f0455a092c6f Mon Sep 17 00:00:00 2001 From: Akshay Deo Date: Fri, 5 Aug 2022 15:48:08 +0530 Subject: [PATCH] ESLint fixes --- docs/api/context-bridge.md | 19 +- lib/renderer/api/context-bridge.ts | 23 +- spec-main/api-context-bridge-spec.ts | 1173 +++++++++----------------- 3 files changed, 404 insertions(+), 811 deletions(-) diff --git a/docs/api/context-bridge.md b/docs/api/context-bridge.md index 966ab40f97c41..93949d374a292 100644 --- a/docs/api/context-bridge.md +++ b/docs/api/context-bridge.md @@ -90,7 +90,7 @@ contextBridge.exposeInMainWorld( ) ``` -An example of complex API exposed in an isolated world is shown below: +An example of API exposed in an isolated world is shown below: ```javascript @@ -104,22 +104,7 @@ 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 - }) - } - } - } + doThing: () => ipcRenderer.send('do-a-thing') } ) diff --git a/lib/renderer/api/context-bridge.ts b/lib/renderer/api/context-bridge.ts index 566d6de9f21e9..5833f15af3c0a 100644 --- a/lib/renderer/api/context-bridge.ts +++ b/lib/renderer/api/context-bridge.ts @@ -1,11 +1,7 @@ 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 = { @@ -26,22 +22,11 @@ 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 }; diff --git a/spec-main/api-context-bridge-spec.ts b/spec-main/api-context-bridge-spec.ts index 5dd102de32df8..610e45d1d60ab 100644 --- a/spec-main/api-context-bridge-spec.ts +++ b/spec-main/api-context-bridge-spec.ts @@ -1,23 +1,17 @@ -import * as cp from 'child_process'; +import { BrowserWindow, ipcMain } from 'electron/main'; +import { contextBridge } from 'electron/renderer'; +import { expect } from 'chai'; import * as fs from 'fs-extra'; import * as http from 'http'; import * as os from 'os'; import * as path from 'path'; +import * as cp from 'child_process'; -import { BrowserWindow, ipcMain } from 'electron/main'; - -import { AddressInfo } from 'net'; import { closeWindow } from './window-helpers'; -import { contextBridge } from 'electron/renderer'; import { emittedOnce } from './events-helpers'; -import { expect } from 'chai'; +import { AddressInfo } from 'net'; -const fixturesPath = path.resolve( - __dirname, - 'fixtures', - 'api', - 'context-bridge' -); +const fixturesPath = path.resolve(__dirname, 'fixtures', 'api', 'context-bridge'); describe('contextBridge', () => { let w: BrowserWindow; @@ -29,13 +23,11 @@ describe('contextBridge', () => { res.setHeader('Content-Type', 'text/html'); res.end(''); }); - await new Promise((resolve) => - server.listen(0, '127.0.0.1', resolve) - ); + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); }); after(async () => { - if (server) await new Promise((resolve) => server.close(resolve)); + if (server) await new Promise(resolve => server.close(resolve)); server = null as any; }); @@ -52,9 +44,7 @@ describe('contextBridge', () => { preload: path.resolve(fixturesPath, 'can-bind-preload.js') } }); - const [, bound] = await emittedOnce(ipcMain, 'context-bridge-bound', () => - w.loadFile(path.resolve(fixturesPath, 'empty.html')) - ); + const [, bound] = await emittedOnce(ipcMain, 'context-bridge-bound', () => w.loadFile(path.resolve(fixturesPath, 'empty.html'))); expect(bound).to.equal(false); }); @@ -66,67 +56,55 @@ describe('contextBridge', () => { preload: path.resolve(fixturesPath, 'can-bind-preload.js') } }); - const [, bound] = await emittedOnce(ipcMain, 'context-bridge-bound', () => - w.loadFile(path.resolve(fixturesPath, 'empty.html')) - ); + const [, bound] = await emittedOnce(ipcMain, 'context-bridge-bound', () => w.loadFile(path.resolve(fixturesPath, 'empty.html'))); expect(bound).to.equal(true); }); - const generateTests = (useSandbox: boolean) => { - const getGCInfo = async (): Promise<{ - trackedValues: number; - }> => { - const [, info] = await emittedOnce(ipcMain, 'gc-info', () => - w.webContents.send('get-gc-info') - ); - return info; - }; + describe('exposeInMainWorld', () => { + const generateTests = (useSandbox: boolean) => { + describe(`with sandbox=${useSandbox}`, () => { + const makeBindingWindow = async (bindingCreator: Function) => { + const preloadContent = `const renderer_1 = require('electron'); + ${useSandbox ? '' : `require('v8').setFlagsFromString('--expose_gc'); + const gc=require('vm').runInNewContext('gc'); + renderer_1.contextBridge.exposeInMainWorld('GCRunner', { + run: () => gc() + });`} + (${bindingCreator.toString()})();`; + const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-spec-preload-')); + dir = tmpDir; + await fs.writeFile(path.resolve(tmpDir, 'preload.js'), preloadContent); + w = new BrowserWindow({ + show: false, + webPreferences: { + contextIsolation: true, + nodeIntegration: true, + sandbox: useSandbox, + preload: path.resolve(tmpDir, 'preload.js'), + additionalArguments: ['--unsafely-expose-electron-internals-for-testing'] + } + }); + await w.loadURL(`http://127.0.0.1:${(server.address() as AddressInfo).port}`); + }; - const forceGCOnWindow = async () => { - w.webContents.debugger.attach(); - await w.webContents.debugger.sendCommand('HeapProfiler.enable'); - await w.webContents.debugger.sendCommand('HeapProfiler.collectGarbage'); - await w.webContents.debugger.sendCommand('HeapProfiler.disable'); - w.webContents.debugger.detach(); - }; - describe('exposeInMainWorld', () => { - const makeBindingWindow = async (bindingCreator: Function) => { - const preloadContent = `const renderer_1 = require('electron'); - ${ - useSandbox - ? '' - : `require('v8').setFlagsFromString('--expose_gc'); - const gc=require('vm').runInNewContext('gc'); - renderer_1.contextBridge.exposeInMainWorld('GCRunner', { - run: () => gc() - });` - } - (${bindingCreator.toString()})();`; - const tmpDir = await fs.mkdtemp( - path.resolve(os.tmpdir(), 'electron-spec-preload-') - ); - dir = tmpDir; - await fs.writeFile(path.resolve(tmpDir, 'preload.js'), preloadContent); - w = new BrowserWindow({ - show: false, - webPreferences: { - contextIsolation: true, - nodeIntegration: true, - sandbox: useSandbox, - preload: path.resolve(tmpDir, 'preload.js'), - additionalArguments: [ - '--unsafely-expose-electron-internals-for-testing' - ] - } - }); - await w.loadURL( - `http://127.0.0.1:${(server.address() as AddressInfo).port}` - ); - }; - const callWithBindings = (fn: Function) => - w.webContents.executeJavaScript(`(${fn.toString()})(window)`); + const callWithBindings = (fn: Function) => + w.webContents.executeJavaScript(`(${fn.toString()})(window)`); + + const getGCInfo = async (): Promise<{ + trackedValues: number; + }> => { + const [, info] = await emittedOnce(ipcMain, 'gc-info', () => w.webContents.send('get-gc-info')); + return info; + }; + + const forceGCOnWindow = async () => { + w.webContents.debugger.attach(); + await w.webContents.debugger.sendCommand('HeapProfiler.enable'); + await w.webContents.debugger.sendCommand('HeapProfiler.collectGarbage'); + await w.webContents.debugger.sendCommand('HeapProfiler.disable'); + w.webContents.debugger.detach(); + }; - describe(`with sandbox=${useSandbox}`, () => { it('should proxy numbers', async () => { await makeBindingWindow(() => { contextBridge.exposeInMainWorld('example', 123); @@ -273,8 +251,7 @@ describe('contextBridge', () => { it('should proxy promises and resolve with the correct value', async () => { await makeBindingWindow(() => { - contextBridge.exposeInMainWorld( - 'example', + contextBridge.exposeInMainWorld('example', Promise.resolve('i-resolved') ); }); @@ -298,10 +275,7 @@ describe('contextBridge', () => { it('should proxy promises and reject with the correct value', async () => { await makeBindingWindow(() => { - contextBridge.exposeInMainWorld( - 'example', - Promise.reject(new Error('i-rejected')) - ); + contextBridge.exposeInMainWorld('example', Promise.reject(new Error('i-rejected'))); }); const result = await callWithBindings(async (root: any) => { try { @@ -311,9 +285,7 @@ describe('contextBridge', () => { return err; } }); - expect(result) - .to.be.an.instanceOf(Error) - .with.property('message', 'i-rejected'); + expect(result).to.be.an.instanceOf(Error).with.property('message', 'i-rejected'); }); it('should proxy nested promises and reject with the correct value', async () => { @@ -330,18 +302,13 @@ describe('contextBridge', () => { return err; } }); - expect(result) - .to.be.an.instanceOf(Error) - .with.property('message', 'i-rejected'); + expect(result).to.be.an.instanceOf(Error).with.property('message', 'i-rejected'); }); it('should proxy promises and resolve with the correct value if it resolves later', async () => { await makeBindingWindow(() => { contextBridge.exposeInMainWorld('example', { - myPromise: () => - new Promise((resolve) => - setTimeout(() => resolve('delayed'), 20) - ) + myPromise: () => new Promise(resolve => setTimeout(() => resolve('delayed'), 20)) }); }); const result = await callWithBindings((root: any) => { @@ -353,10 +320,7 @@ describe('contextBridge', () => { it('should proxy nested promises correctly', async () => { await makeBindingWindow(() => { contextBridge.exposeInMainWorld('example', { - myPromise: () => - new Promise((resolve) => - setTimeout(() => resolve(Promise.resolve(123)), 20) - ) + myPromise: () => new Promise(resolve => setTimeout(() => resolve(Promise.resolve(123)), 20)) }); }); const result = await callWithBindings((root: any) => { @@ -375,12 +339,7 @@ describe('contextBridge', () => { }); }); const result = await callWithBindings(async (root: any) => { - return [ - root.example.getNumber(), - root.example.getString(), - root.example.getBoolean(), - await root.example.getPromise() - ]; + return [root.example.getNumber(), root.example.getString(), root.example.getBoolean(), await root.example.getPromise()]; }); expect(result).to.deep.equal([123, 'help', false, 'promise']); }); @@ -408,9 +367,7 @@ describe('contextBridge', () => { it('should properly handle errors thrown in proxied functions', async () => { await makeBindingWindow(() => { - contextBridge.exposeInMainWorld('example', () => { - throw new Error('oh no'); - }); + contextBridge.exposeInMainWorld('example', () => { throw new Error('oh no'); }); }); const result = await callWithBindings(async (root: any) => { try { @@ -429,11 +386,7 @@ describe('contextBridge', () => { }); }); const result = await callWithBindings(async (root: any) => { - return [ - root.example.doThing(), - root.example.doThing(), - root.example.doThing() - ]; + return [root.example.doThing(), root.example.doThing(), root.example.doThing()]; }); expect(result).to.deep.equal([123, 123, 123]); }); @@ -457,9 +410,7 @@ describe('contextBridge', () => { }); }); const result = await callWithBindings((root: any) => { - return root.example.getPromiseValue( - Promise.resolve('my-proxied-value') - ); + return root.example.getPromiseValue(Promise.resolve('my-proxied-value')); }); expect(result).to.equal('my-proxied-value'); }); @@ -473,12 +424,7 @@ describe('contextBridge', () => { }); }); const result = await callWithBindings(async (root: any) => { - return [ - root.example[1], - root.example[2], - root.example[3], - Array.isArray(root.example) - ]; + return [root.example[1], root.example[2], root.example[3], Array.isArray(root.example)]; }); expect(result).to.deep.equal([123, 456, 789, false]); }); @@ -531,10 +477,7 @@ describe('contextBridge', () => { const result = await callWithBindings((root: any) => { return root.isSymbol(root.symbol); }); - expect(result).to.equal( - true, - 'symbols should be equal across contexts' - ); + expect(result).to.equal(true, 'symbols should be equal across contexts'); }); it('should proxy symbols such that symbol equality works', async () => { @@ -548,10 +491,7 @@ describe('contextBridge', () => { const result = await callWithBindings((root: any) => { return root.example.isSymbol(root.example.getSymbol()); }); - expect(result).to.equal( - true, - 'symbols should be equal across contexts' - ); + expect(result).to.equal(true, 'symbols should be equal across contexts'); }); it('should proxy symbols such that symbol key lookup works', async () => { @@ -565,10 +505,7 @@ describe('contextBridge', () => { const result = await callWithBindings((root: any) => { return root.example.getObject()[root.example.getSymbol()]; }); - expect(result).to.equal( - 123, - 'symbols key lookup should work across contexts' - ); + expect(result).to.equal(123, 'symbols key lookup should work across contexts'); }); it('should proxy typed arrays', async () => { @@ -616,11 +553,7 @@ describe('contextBridge', () => { }); }); const result = await callWithBindings((root: any) => { - return [ - root.example.o.value, - root.example.o.o.value, - root.example.o.o.o.value - ]; + return [root.example.o.value, root.example.o.o.value, root.example.o.o.o.value]; }); expect(result).to.deep.equal([135, 135, 135]); }); @@ -632,11 +565,7 @@ describe('contextBridge', () => { }); }); const result = await callWithBindings((root: any) => { - return [ - root.example.getElem().tagName, - root.example.getElem().constructor.name, - typeof root.example.getElem().querySelector - ]; + return [root.example.getElem().tagName, root.example.getElem().constructor.name, typeof root.example.getElem().querySelector]; }); expect(result).to.deep.equal(['BODY', 'HTMLBodyElement', 'function']); }); @@ -646,11 +575,7 @@ describe('contextBridge', () => { contextBridge.exposeInMainWorld('example', { getElemInfo: (fn: Function) => { const elem = fn(); - return [ - elem.tagName, - elem.constructor.name, - typeof elem.querySelector - ]; + return [elem.tagName, elem.constructor.name, typeof elem.querySelector]; } }); }); @@ -692,17 +617,10 @@ describe('contextBridge', () => { it('should release the global hold on methods sent across contexts', async () => { await makeBindingWindow(() => { const trackedValues: WeakRef[] = []; - require('electron').ipcRenderer.on('get-gc-info', (e) => - e.sender.send('gc-info', { - trackedValues: trackedValues.filter((value) => value.deref()) - .length - }) - ); + require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', { trackedValues: trackedValues.filter(value => value.deref()).length })); contextBridge.exposeInMainWorld('example', { getFunction: () => () => 123, - track: (value: object) => { - trackedValues.push(new WeakRef(value)); - } + track: (value: object) => { trackedValues.push(new WeakRef(value)); } }); }); await callWithBindings(async (root: any) => { @@ -727,24 +645,14 @@ describe('contextBridge', () => { it('should not leak the global hold on methods sent across contexts when reloading a sandboxed renderer', async () => { await makeBindingWindow(() => { const trackedValues: WeakRef[] = []; - require('electron').ipcRenderer.on('get-gc-info', (e) => - e.sender.send('gc-info', { - trackedValues: trackedValues.filter((value) => value.deref()) - .length - }) - ); + require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', { trackedValues: trackedValues.filter(value => value.deref()).length })); contextBridge.exposeInMainWorld('example', { getFunction: () => () => 123, - track: (value: object) => { - trackedValues.push(new WeakRef(value)); - } + track: (value: object) => { trackedValues.push(new WeakRef(value)); } }); require('electron').ipcRenderer.send('window-ready-for-tasking'); }); - const loadPromise = emittedOnce( - ipcMain, - 'window-ready-for-tasking' - ); + const loadPromise = emittedOnce(ipcMain, 'window-ready-for-tasking'); expect((await getGCInfo()).trackedValues).to.equal(0); await callWithBindings((root: any) => { root.example.track(root.example.getFunction()); @@ -786,10 +694,9 @@ describe('contextBridge', () => { it('should work with complex nested methods and promises', async () => { await makeBindingWindow(() => { contextBridge.exposeInMainWorld('example', { - first: (second: Function) => - second((fourth: Function) => { - return fourth(); - }) + first: (second: Function) => second((fourth: Function) => { + return fourth(); + }) }); }); const result = await callWithBindings((root: any) => { @@ -802,8 +709,8 @@ describe('contextBridge', () => { it('should work with complex nested methods and promises attached directly to the global', async () => { await makeBindingWindow(() => { - contextBridge.exposeInMainWorld('example', (second: Function) => - second((fourth: Function) => { + contextBridge.exposeInMainWorld('example', + (second: Function) => second((fourth: Function) => { return fourth(); }) ); @@ -874,32 +781,13 @@ describe('contextBridge', () => { } return null; }; - const normalIsError = - Object.getPrototypeOf(getError(root.example.throwNormal)) === - Error.prototype; - const weirdIsError = - Object.getPrototypeOf(getError(root.example.throwWeird)) === - Error.prototype; - const notClonableIsError = - Object.getPrototypeOf(getError(root.example.throwNotClonable)) === - Error.prototype; - const argumentConvertIsError = - Object.getPrototypeOf( - getError(() => - root.example.argumentConvert(Object(Symbol('test'))) - ) - ) === Error.prototype; - return [ - normalIsError, - weirdIsError, - notClonableIsError, - argumentConvertIsError - ]; + const normalIsError = Object.getPrototypeOf(getError(root.example.throwNormal)) === Error.prototype; + const weirdIsError = Object.getPrototypeOf(getError(root.example.throwWeird)) === Error.prototype; + const notClonableIsError = Object.getPrototypeOf(getError(root.example.throwNotClonable)) === Error.prototype; + const argumentConvertIsError = Object.getPrototypeOf(getError(() => root.example.argumentConvert(Object(Symbol('test'))))) === Error.prototype; + return [normalIsError, weirdIsError, notClonableIsError, argumentConvertIsError]; }); - expect(result).to.deep.equal( - [true, true, true, true], - 'should all be errors in the current context' - ); + expect(result).to.deep.equal([true, true, true, true], 'should all be errors in the current context'); }); it('should not leak prototypes', async () => { @@ -916,26 +804,14 @@ describe('contextBridge', () => { getString: () => 'string', getBoolean: () => true, getArr: () => [123, 'string', true, ['foo']], - getPromise: async () => ({ - number: 123, - string: 'string', - boolean: true, - fn: () => 'string', - arr: [123, 'string', true, ['foo']] - }), + getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']] }), getFunctionFromFunction: async () => () => null, object: { number: 123, string: 'string', boolean: true, arr: [123, 'string', true, ['foo']], - getPromise: async () => ({ - number: 123, - string: 'string', - boolean: true, - fn: () => 'string', - arr: [123, 'string', true, ['foo']] - }) + getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']] }) }, receiveArguments: (fn: any) => fn({ key: 'value' }), symbolKeyed: { @@ -948,14 +824,10 @@ describe('contextBridge', () => { const result = await callWithBindings(async (root: any) => { const { example } = root; let arg: any; - example.receiveArguments((o: any) => { - arg = o; - }); + example.receiveArguments((o: any) => { arg = o; }); const protoChecks = [ - ...Object.keys(example).map((key) => [key, String]), - ...Object.getOwnPropertySymbols(example.symbolKeyed).map( - (key) => [key, Symbol] - ), + ...Object.keys(example).map(key => [key, String]), + ...Object.getOwnPropertySymbols(example.symbolKeyed).map(key => [key, Symbol]), [example, Object], [example.number, Number], [example.string, String], @@ -1023,16 +895,11 @@ describe('contextBridge', () => { [example.getBlob(), Blob] ]; return { - protoMatches: protoChecks.map( - ([a, Constructor]) => - Object.getPrototypeOf(a) === Constructor.prototype - ) + protoMatches: protoChecks.map(([a, Constructor]) => Object.getPrototypeOf(a) === Constructor.prototype) }; }); // Every protomatch should be true - expect(result.protoMatches).to.deep.equal( - result.protoMatches.map(() => true) - ); + expect(result.protoMatches).to.deep.equal(result.protoMatches.map(() => true)); }); it('should not leak prototypes when attaching directly to the global', async () => { @@ -1049,13 +916,7 @@ describe('contextBridge', () => { getString: () => 'string', getBoolean: () => true, getArr: () => [123, 'string', true, ['foo']], - getPromise: async () => ({ - number: 123, - string: 'string', - boolean: true, - fn: () => 'string', - arr: [123, 'string', true, ['foo']] - }), + getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']] }), getFunctionFromFunction: async () => () => null, getError: () => new Error('foo'), getWeirdError: () => { @@ -1068,13 +929,7 @@ describe('contextBridge', () => { string: 'string', boolean: true, arr: [123, 'string', true, ['foo']], - getPromise: async () => ({ - number: 123, - string: 'string', - boolean: true, - fn: () => 'string', - arr: [123, 'string', true, ['foo']] - }) + getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']] }) }, receiveArguments: (fn: any) => fn({ key: 'value' }), symbolKeyed: { @@ -1098,14 +953,10 @@ describe('contextBridge', () => { } let arg: any; - cleanedRoot.receiveArguments((o: any) => { - arg = o; - }); + cleanedRoot.receiveArguments((o: any) => { arg = o; }); const protoChecks = [ - ...Object.keys(cleanedRoot).map((key) => [key, String]), - ...Object.getOwnPropertySymbols(cleanedRoot.symbolKeyed).map( - (key) => [key, Symbol] - ), + ...Object.keys(cleanedRoot).map(key => [key, String]), + ...Object.getOwnPropertySymbols(cleanedRoot.symbolKeyed).map(key => [key, Symbol]), [cleanedRoot, Object], [cleanedRoot.number, Number], [cleanedRoot.string, String], @@ -1175,61 +1026,250 @@ describe('contextBridge', () => { [arg.key, String] ]; return { - protoMatches: protoChecks.map( - ([a, Constructor]) => - Object.getPrototypeOf(a) === Constructor.prototype - ) + protoMatches: protoChecks.map(([a, Constructor]) => Object.getPrototypeOf(a) === Constructor.prototype) }; }); // Every protomatch should be true - expect(result.protoMatches).to.deep.equal( - result.protoMatches.map(() => true) - ); + expect(result.protoMatches).to.deep.equal(result.protoMatches.map(() => true)); + }); + + describe('internalContextBridge', () => { + describe('overrideGlobalValueFromIsolatedWorld', () => { + it('should override top level properties', async () => { + await makeBindingWindow(() => { + contextBridge.internalContextBridge!.overrideGlobalValueFromIsolatedWorld(['open'], () => ({ you: 'are a wizard' })); + }); + const result = await callWithBindings(async (root: any) => { + return root.open(); + }); + expect(result).to.deep.equal({ you: 'are a wizard' }); + }); + + it('should override deep properties', async () => { + await makeBindingWindow(() => { + contextBridge.internalContextBridge!.overrideGlobalValueFromIsolatedWorld(['document', 'foo'], () => 'I am foo'); + }); + const result = await callWithBindings(async (root: any) => { + return root.document.foo(); + }); + expect(result).to.equal('I am foo'); + }); + }); + + describe('overrideGlobalPropertyFromIsolatedWorld', () => { + it('should call the getter correctly', async () => { + await makeBindingWindow(() => { + let callCount = 0; + const getter = () => { + callCount++; + return true; + }; + contextBridge.internalContextBridge!.overrideGlobalPropertyFromIsolatedWorld(['isFun'], getter); + contextBridge.exposeInMainWorld('foo', { + callCount: () => callCount + }); + }); + const result = await callWithBindings(async (root: any) => { + return [root.isFun, root.foo.callCount()]; + }); + expect(result[0]).to.equal(true); + expect(result[1]).to.equal(1); + }); + + it('should not make a setter if none is provided', async () => { + await makeBindingWindow(() => { + contextBridge.internalContextBridge!.overrideGlobalPropertyFromIsolatedWorld(['isFun'], () => true); + }); + const result = await callWithBindings(async (root: any) => { + root.isFun = 123; + return root.isFun; + }); + expect(result).to.equal(true); + }); + + it('should call the setter correctly', async () => { + await makeBindingWindow(() => { + const callArgs: any[] = []; + const setter = (...args: any[]) => { + callArgs.push(args); + return true; + }; + contextBridge.internalContextBridge!.overrideGlobalPropertyFromIsolatedWorld(['isFun'], () => true, setter); + contextBridge.exposeInMainWorld('foo', { + callArgs: () => callArgs + }); + }); + const result = await callWithBindings(async (root: any) => { + root.isFun = 123; + return root.foo.callArgs(); + }); + expect(result).to.have.lengthOf(1); + expect(result[0]).to.have.lengthOf(1); + expect(result[0][0]).to.equal(123); + }); + }); + + describe('overrideGlobalValueWithDynamicPropsFromIsolatedWorld', () => { + it('should not affect normal values', async () => { + await makeBindingWindow(() => { + contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['thing'], { + a: 123, + b: () => 2, + c: () => ({ d: 3 }) + }); + }); + const result = await callWithBindings(async (root: any) => { + return [root.thing.a, root.thing.b(), root.thing.c()]; + }); + expect(result).to.deep.equal([123, 2, { d: 3 }]); + }); + + it('should work with getters', async () => { + await makeBindingWindow(() => { + contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['thing'], { + get foo () { + return 'hi there'; + } + }); + }); + const result = await callWithBindings(async (root: any) => { + return root.thing.foo; + }); + expect(result).to.equal('hi there'); + }); + + it('should work with nested getters', async () => { + await makeBindingWindow(() => { + contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['thing'], { + get foo () { + return { + get bar () { + return 'hi there'; + } + }; + } + }); + }); + const result = await callWithBindings(async (root: any) => { + return root.thing.foo.bar; + }); + expect(result).to.equal('hi there'); + }); + + it('should work with setters', async () => { + await makeBindingWindow(() => { + let a: any = null; + contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['thing'], { + get foo () { + return a; + }, + set foo (arg: any) { + a = arg + 1; + } + }); + }); + const result = await callWithBindings(async (root: any) => { + root.thing.foo = 123; + return root.thing.foo; + }); + expect(result).to.equal(124); + }); + + it('should work with nested getter / setter combos', async () => { + await makeBindingWindow(() => { + let a: any = null; + contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['thing'], { + get thingy () { + return { + get foo () { + return a; + }, + set foo (arg: any) { + a = arg + 1; + } + }; + } + }); + }); + const result = await callWithBindings(async (root: any) => { + root.thing.thingy.foo = 123; + return root.thing.thingy.foo; + }); + expect(result).to.equal(124); + }); + + it('should work with deep properties', async () => { + await makeBindingWindow(() => { + contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['thing'], { + a: () => ({ + get foo () { + return 'still here'; + } + }) + }); + }); + const result = await callWithBindings(async (root: any) => { + return root.thing.a().foo; + }); + expect(result).to.equal('still here'); + }); + }); }); }); - }); - describe('exposeInIsolatedWorld', () => { - const makeBindingWindow = async (bindingCreator: Function) => { - const preloadContent = `const renderer_1 = require('electron'); - ${ - useSandbox - ? '' - : `require('v8').setFlagsFromString('--expose_gc'); - const gc=require('vm').runInNewContext('gc'); - renderer_1.webFrame.setIsolatedWorldInfo(1004, { - name: "Isolated World" - }); - renderer_1.contextBridge.exposeInIsolatedWorld(1004,'GCRunner', { - run: () => gc() - });` - } - (${bindingCreator.toString()})();`; - const tmpDir = await fs.mkdtemp( - path.resolve(os.tmpdir(), 'electron-spec-preload-') - ); - dir = tmpDir; - await fs.writeFile(path.resolve(tmpDir, 'preload.js'), preloadContent); - w = new BrowserWindow({ - show: false, - webPreferences: { - contextIsolation: true, - nodeIntegration: true, - sandbox: useSandbox, - preload: path.resolve(tmpDir, 'preload.js'), - additionalArguments: [ - '--unsafely-expose-electron-internals-for-testing' - ] - } - }); - await w.loadURL( - `http://127.0.0.1:${(server.address() as AddressInfo).port}` - ); - }; - const callWithBindings = (fn: Function) => - w.webContents.executeJavaScriptInIsolatedWorld(1004, [{ - code: `(${fn.toString()})(window)` - }]); + }; + + generateTests(true); + generateTests(false); + }); + + describe('exposeInIsolatedWorld', () => { + const generateTests = (useSandbox: boolean) => { describe(`with sandbox=${useSandbox}`, () => { + const makeBindingWindow = async (bindingCreator: Function) => { + const preloadContent = `const renderer_1 = require('electron'); + ${useSandbox ? '' : `require('v8').setFlagsFromString('--expose_gc'); + const gc=require('vm').runInNewContext('gc'); + renderer_1.webFrame.setIsolatedWorldInfo(1004, { + name: "Isolated World" + }); + renderer_1.contextBridge.exposeInIsolatedWorld(1004, 'GCRunner', { + run: () => gc() + });`} + (${bindingCreator.toString()})();`; + const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-spec-preload-')); + dir = tmpDir; + await fs.writeFile(path.resolve(tmpDir, 'preload.js'), preloadContent); + w = new BrowserWindow({ + show: false, + webPreferences: { + contextIsolation: true, + nodeIntegration: true, + sandbox: useSandbox, + preload: path.resolve(tmpDir, 'preload.js'), + additionalArguments: ['--unsafely-expose-electron-internals-for-testing'] + } + }); + await w.loadURL(`http://127.0.0.1:${(server.address() as AddressInfo).port}`); + }; + + const callWithBindings = (fn: Function) => + w.webContents.executeJavaScriptInIsolatedWorld(1004, [{ code: `(${fn.toString()})(window)` }]); + + const getGCInfo = async (): Promise<{ + trackedValues: number; + }> => { + const [, info] = await emittedOnce(ipcMain, 'gc-info', () => w.webContents.send('get-gc-info')); + return info; + }; + + const forceGCOnWindow = async () => { + w.webContents.debugger.attach(); + await w.webContents.debugger.sendCommand('HeapProfiler.enable'); + await w.webContents.debugger.sendCommand('HeapProfiler.collectGarbage'); + await w.webContents.debugger.sendCommand('HeapProfiler.disable'); + w.webContents.debugger.detach(); + }; + it('should proxy numbers', async () => { await makeBindingWindow(() => { contextBridge.exposeInIsolatedWorld(1004, 'example', 123); @@ -1300,10 +1340,7 @@ describe('contextBridge', () => { it('should proxy arrays', async () => { await makeBindingWindow(() => { - contextBridge.exposeInIsolatedWorld(1004, 'example', [ - 123, - 'my-words' - ]); + contextBridge.exposeInIsolatedWorld(1004, 'example', [123, 'my-words']); }); const result = await callWithBindings((root: any) => { return [root.example, Array.isArray(root.example)]; @@ -1325,10 +1362,7 @@ describe('contextBridge', () => { it('should make arrays immutable', async () => { await makeBindingWindow(() => { - contextBridge.exposeInIsolatedWorld(1004, 'example', [ - 123, - 'my-words' - ]); + contextBridge.exposeInIsolatedWorld(1004, 'example', [123, 'my-words']); }); const immutable = await callWithBindings((root: any) => { try { @@ -1382,9 +1416,7 @@ describe('contextBridge', () => { it('should proxy promises and resolve with the correct value', async () => { await makeBindingWindow(() => { - contextBridge.exposeInIsolatedWorld( - 1004, - 'example', + contextBridge.exposeInIsolatedWorld(1004, 'example', Promise.resolve('i-resolved') ); }); @@ -1408,11 +1440,7 @@ describe('contextBridge', () => { it('should proxy promises and reject with the correct value', async () => { await makeBindingWindow(() => { - contextBridge.exposeInIsolatedWorld( - 1004, - 'example', - Promise.reject(new Error('i-rejected')) - ); + contextBridge.exposeInIsolatedWorld(1004, 'example', Promise.reject(new Error('i-rejected'))); }); const result = await callWithBindings(async (root: any) => { try { @@ -1422,9 +1450,7 @@ describe('contextBridge', () => { return err; } }); - expect(result) - .to.be.an.instanceOf(Error) - .with.property('message', 'i-rejected'); + expect(result).to.be.an.instanceOf(Error).with.property('message', 'i-rejected'); }); it('should proxy nested promises and reject with the correct value', async () => { @@ -1441,18 +1467,13 @@ describe('contextBridge', () => { return err; } }); - expect(result) - .to.be.an.instanceOf(Error) - .with.property('message', 'i-rejected'); + expect(result).to.be.an.instanceOf(Error).with.property('message', 'i-rejected'); }); it('should proxy promises and resolve with the correct value if it resolves later', async () => { await makeBindingWindow(() => { contextBridge.exposeInIsolatedWorld(1004, 'example', { - myPromise: () => - new Promise((resolve) => - setTimeout(() => resolve('delayed'), 20) - ) + myPromise: () => new Promise(resolve => setTimeout(() => resolve('delayed'), 20)) }); }); const result = await callWithBindings((root: any) => { @@ -1464,10 +1485,7 @@ describe('contextBridge', () => { it('should proxy nested promises correctly', async () => { await makeBindingWindow(() => { contextBridge.exposeInIsolatedWorld(1004, 'example', { - myPromise: () => - new Promise((resolve) => - setTimeout(() => resolve(Promise.resolve(123)), 20) - ) + myPromise: () => new Promise(resolve => setTimeout(() => resolve(Promise.resolve(123)), 20)) }); }); const result = await callWithBindings((root: any) => { @@ -1486,23 +1504,14 @@ describe('contextBridge', () => { }); }); const result = await callWithBindings(async (root: any) => { - return [ - root.example.getNumber(), - root.example.getString(), - root.example.getBoolean(), - await root.example.getPromise() - ]; + return [root.example.getNumber(), root.example.getString(), root.example.getBoolean(), await root.example.getPromise()]; }); expect(result).to.deep.equal([123, 'help', false, 'promise']); }); it('should proxy functions', async () => { await makeBindingWindow(() => { - contextBridge.exposeInIsolatedWorld( - 1004, - 'example', - () => 'return-value' - ); + contextBridge.exposeInIsolatedWorld(1004, 'example', () => 'return-value'); }); const result = await callWithBindings(async (root: any) => { return root.example(); @@ -1512,11 +1521,7 @@ describe('contextBridge', () => { it('should not double-proxy functions when they are returned to their origin side of the bridge', async () => { await makeBindingWindow(() => { - contextBridge.exposeInIsolatedWorld( - 1004, - 'example', - (fn: any) => fn - ); + contextBridge.exposeInIsolatedWorld(1004, 'example', (fn: any) => fn); }); const result = await callWithBindings(async (root: any) => { const fn = () => null; @@ -1527,9 +1532,7 @@ describe('contextBridge', () => { it('should properly handle errors thrown in proxied functions', async () => { await makeBindingWindow(() => { - contextBridge.exposeInIsolatedWorld(1004, 'example', () => { - throw new Error('oh no'); - }); + contextBridge.exposeInIsolatedWorld(1004, 'example', () => { throw new Error('oh no'); }); }); const result = await callWithBindings(async (root: any) => { try { @@ -1548,11 +1551,7 @@ describe('contextBridge', () => { }); }); const result = await callWithBindings(async (root: any) => { - return [ - root.example.doThing(), - root.example.doThing(), - root.example.doThing() - ]; + return [root.example.doThing(), root.example.doThing(), root.example.doThing()]; }); expect(result).to.deep.equal([123, 123, 123]); }); @@ -1576,9 +1575,7 @@ describe('contextBridge', () => { }); }); const result = await callWithBindings((root: any) => { - return root.example.getPromiseValue( - Promise.resolve('my-proxied-value') - ); + return root.example.getPromiseValue(Promise.resolve('my-proxied-value')); }); expect(result).to.equal('my-proxied-value'); }); @@ -1592,12 +1589,7 @@ describe('contextBridge', () => { }); }); const result = await callWithBindings(async (root: any) => { - return [ - root.example[1], - root.example[2], - root.example[3], - Array.isArray(root.example) - ]; + return [root.example[1], root.example[2], root.example[3], Array.isArray(root.example)]; }); expect(result).to.deep.equal([123, 456, 789, false]); }); @@ -1650,10 +1642,7 @@ describe('contextBridge', () => { const result = await callWithBindings((root: any) => { return root.isSymbol(root.symbol); }); - expect(result).to.equal( - true, - 'symbols should be equal across contexts' - ); + expect(result).to.equal(true, 'symbols should be equal across contexts'); }); it('should proxy symbols such that symbol equality works', async () => { @@ -1667,10 +1656,7 @@ describe('contextBridge', () => { const result = await callWithBindings((root: any) => { return root.example.isSymbol(root.example.getSymbol()); }); - expect(result).to.equal( - true, - 'symbols should be equal across contexts' - ); + expect(result).to.equal(true, 'symbols should be equal across contexts'); }); it('should proxy symbols such that symbol key lookup works', async () => { @@ -1684,19 +1670,12 @@ describe('contextBridge', () => { const result = await callWithBindings((root: any) => { return root.example.getObject()[root.example.getSymbol()]; }); - expect(result).to.equal( - 123, - 'symbols key lookup should work across contexts' - ); + expect(result).to.equal(123, 'symbols key lookup should work across contexts'); }); it('should proxy typed arrays', async () => { await makeBindingWindow(() => { - contextBridge.exposeInIsolatedWorld( - 1004, - 'example', - new Uint8Array(100) - ); + contextBridge.exposeInIsolatedWorld(1004, 'example', new Uint8Array(100)); }); const result = await callWithBindings((root: any) => { return Object.getPrototypeOf(root.example) === Uint8Array.prototype; @@ -1739,11 +1718,7 @@ describe('contextBridge', () => { }); }); const result = await callWithBindings((root: any) => { - return [ - root.example.o.value, - root.example.o.o.value, - root.example.o.o.o.value - ]; + return [root.example.o.value, root.example.o.o.value, root.example.o.o.o.value]; }); expect(result).to.deep.equal([135, 135, 135]); }); @@ -1755,11 +1730,7 @@ describe('contextBridge', () => { }); }); const result = await callWithBindings((root: any) => { - return [ - root.example.getElem().tagName, - root.example.getElem().constructor.name, - typeof root.example.getElem().querySelector - ]; + return [root.example.getElem().tagName, root.example.getElem().constructor.name, typeof root.example.getElem().querySelector]; }); expect(result).to.deep.equal(['BODY', 'HTMLBodyElement', 'function']); }); @@ -1769,11 +1740,7 @@ describe('contextBridge', () => { contextBridge.exposeInIsolatedWorld(1004, 'example', { getElemInfo: (fn: Function) => { const elem = fn(); - return [ - elem.tagName, - elem.constructor.name, - typeof elem.querySelector - ]; + return [elem.tagName, elem.constructor.name, typeof elem.querySelector]; } }); }); @@ -1815,17 +1782,10 @@ describe('contextBridge', () => { it('should release the global hold on methods sent across contexts', async () => { await makeBindingWindow(() => { const trackedValues: WeakRef[] = []; - require('electron').ipcRenderer.on('get-gc-info', (e) => - e.sender.send('gc-info', { - trackedValues: trackedValues.filter((value) => value.deref()) - .length - }) - ); + require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', { trackedValues: trackedValues.filter(value => value.deref()).length })); contextBridge.exposeInIsolatedWorld(1004, 'example', { getFunction: () => () => 123, - track: (value: object) => { - trackedValues.push(new WeakRef(value)); - } + track: (value: object) => { trackedValues.push(new WeakRef(value)); } }); }); await callWithBindings(async (root: any) => { @@ -1850,24 +1810,14 @@ describe('contextBridge', () => { it('should not leak the global hold on methods sent across contexts when reloading a sandboxed renderer', async () => { await makeBindingWindow(() => { const trackedValues: WeakRef[] = []; - require('electron').ipcRenderer.on('get-gc-info', (e) => - e.sender.send('gc-info', { - trackedValues: trackedValues.filter((value) => value.deref()) - .length - }) - ); + require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', { trackedValues: trackedValues.filter(value => value.deref()).length })); contextBridge.exposeInIsolatedWorld(1004, 'example', { getFunction: () => () => 123, - track: (value: object) => { - trackedValues.push(new WeakRef(value)); - } + track: (value: object) => { trackedValues.push(new WeakRef(value)); } }); require('electron').ipcRenderer.send('window-ready-for-tasking'); }); - const loadPromise = emittedOnce( - ipcMain, - 'window-ready-for-tasking' - ); + const loadPromise = emittedOnce(ipcMain, 'window-ready-for-tasking'); expect((await getGCInfo()).trackedValues).to.equal(0); await callWithBindings((root: any) => { root.example.track(root.example.getFunction()); @@ -1909,10 +1859,9 @@ describe('contextBridge', () => { it('should work with complex nested methods and promises', async () => { await makeBindingWindow(() => { contextBridge.exposeInIsolatedWorld(1004, 'example', { - first: (second: Function) => - second((fourth: Function) => { - return fourth(); - }) + first: (second: Function) => second((fourth: Function) => { + return fourth(); + }) }); }); const result = await callWithBindings((root: any) => { @@ -1925,13 +1874,10 @@ describe('contextBridge', () => { it('should work with complex nested methods and promises attached directly to the global', async () => { await makeBindingWindow(() => { - contextBridge.exposeInIsolatedWorld( - 1004, - 'example', - (second: Function) => - second((fourth: Function) => { - return fourth(); - }) + contextBridge.exposeInIsolatedWorld(1004, 'example', + (second: Function) => second((fourth: Function) => { + return fourth(); + }) ); }); const result = await callWithBindings((root: any) => { @@ -2000,32 +1946,13 @@ describe('contextBridge', () => { } return null; }; - const normalIsError = - Object.getPrototypeOf(getError(root.example.throwNormal)) === - Error.prototype; - const weirdIsError = - Object.getPrototypeOf(getError(root.example.throwWeird)) === - Error.prototype; - const notClonableIsError = - Object.getPrototypeOf(getError(root.example.throwNotClonable)) === - Error.prototype; - const argumentConvertIsError = - Object.getPrototypeOf( - getError(() => - root.example.argumentConvert(Object(Symbol('test'))) - ) - ) === Error.prototype; - return [ - normalIsError, - weirdIsError, - notClonableIsError, - argumentConvertIsError - ]; + const normalIsError = Object.getPrototypeOf(getError(root.example.throwNormal)) === Error.prototype; + const weirdIsError = Object.getPrototypeOf(getError(root.example.throwWeird)) === Error.prototype; + const notClonableIsError = Object.getPrototypeOf(getError(root.example.throwNotClonable)) === Error.prototype; + const argumentConvertIsError = Object.getPrototypeOf(getError(() => root.example.argumentConvert(Object(Symbol('test'))))) === Error.prototype; + return [normalIsError, weirdIsError, notClonableIsError, argumentConvertIsError]; }); - expect(result).to.deep.equal( - [true, true, true, true], - 'should all be errors in the current context' - ); + expect(result).to.deep.equal([true, true, true, true], 'should all be errors in the current context'); }); it('should not leak prototypes', async () => { @@ -2042,26 +1969,14 @@ describe('contextBridge', () => { getString: () => 'string', getBoolean: () => true, getArr: () => [123, 'string', true, ['foo']], - getPromise: async () => ({ - number: 123, - string: 'string', - boolean: true, - fn: () => 'string', - arr: [123, 'string', true, ['foo']] - }), + getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']] }), getFunctionFromFunction: async () => () => null, object: { number: 123, string: 'string', boolean: true, arr: [123, 'string', true, ['foo']], - getPromise: async () => ({ - number: 123, - string: 'string', - boolean: true, - fn: () => 'string', - arr: [123, 'string', true, ['foo']] - }) + getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']] }) }, receiveArguments: (fn: any) => fn({ key: 'value' }), symbolKeyed: { @@ -2074,14 +1989,10 @@ describe('contextBridge', () => { const result = await callWithBindings(async (root: any) => { const { example } = root; let arg: any; - example.receiveArguments((o: any) => { - arg = o; - }); + example.receiveArguments((o: any) => { arg = o; }); const protoChecks = [ - ...Object.keys(example).map((key) => [key, String]), - ...Object.getOwnPropertySymbols(example.symbolKeyed).map( - (key) => [key, Symbol] - ), + ...Object.keys(example).map(key => [key, String]), + ...Object.getOwnPropertySymbols(example.symbolKeyed).map(key => [key, Symbol]), [example, Object], [example.number, Number], [example.string, String], @@ -2149,16 +2060,11 @@ describe('contextBridge', () => { [example.getBlob(), Blob] ]; return { - protoMatches: protoChecks.map( - ([a, Constructor]) => - Object.getPrototypeOf(a) === Constructor.prototype - ) + protoMatches: protoChecks.map(([a, Constructor]) => Object.getPrototypeOf(a) === Constructor.prototype) }; }); // Every protomatch should be true - expect(result.protoMatches).to.deep.equal( - result.protoMatches.map(() => true) - ); + expect(result.protoMatches).to.deep.equal(result.protoMatches.map(() => true)); }); it('should not leak prototypes when attaching directly to the global', async () => { @@ -2175,13 +2081,7 @@ describe('contextBridge', () => { getString: () => 'string', getBoolean: () => true, getArr: () => [123, 'string', true, ['foo']], - getPromise: async () => ({ - number: 123, - string: 'string', - boolean: true, - fn: () => 'string', - arr: [123, 'string', true, ['foo']] - }), + getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']] }), getFunctionFromFunction: async () => () => null, getError: () => new Error('foo'), getWeirdError: () => { @@ -2194,13 +2094,7 @@ describe('contextBridge', () => { string: 'string', boolean: true, arr: [123, 'string', true, ['foo']], - getPromise: async () => ({ - number: 123, - string: 'string', - boolean: true, - fn: () => 'string', - arr: [123, 'string', true, ['foo']] - }) + getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']] }) }, receiveArguments: (fn: any) => fn({ key: 'value' }), symbolKeyed: { @@ -2224,14 +2118,10 @@ describe('contextBridge', () => { } let arg: any; - cleanedRoot.receiveArguments((o: any) => { - arg = o; - }); + cleanedRoot.receiveArguments((o: any) => { arg = o; }); const protoChecks = [ - ...Object.keys(cleanedRoot).map((key) => [key, String]), - ...Object.getOwnPropertySymbols(cleanedRoot.symbolKeyed).map( - (key) => [key, Symbol] - ), + ...Object.keys(cleanedRoot).map(key => [key, String]), + ...Object.getOwnPropertySymbols(cleanedRoot.symbolKeyed).map(key => [key, Symbol]), [cleanedRoot, Object], [cleanedRoot.number, Number], [cleanedRoot.string, String], @@ -2301,289 +2191,27 @@ describe('contextBridge', () => { [arg.key, String] ]; return { - protoMatches: protoChecks.map( - ([a, Constructor]) => - Object.getPrototypeOf(a) === Constructor.prototype - ) + protoMatches: protoChecks.map(([a, Constructor]) => Object.getPrototypeOf(a) === Constructor.prototype) }; }); // Every protomatch should be true - expect(result.protoMatches).to.deep.equal( - result.protoMatches.map(() => true) - ); - }); - }); - }); - describe('internalContextBridge', () => { - const makeBindingWindow = async (bindingCreator: Function) => { - const preloadContent = `const renderer_1 = require('electron'); - ${ - useSandbox - ? '' - : `require('v8').setFlagsFromString('--expose_gc'); - const gc=require('vm').runInNewContext('gc'); - renderer_1.contextBridge.exposeInMainWorld('GCRunner', { - run: () => gc() - });` - } - (${bindingCreator.toString()})();`; - const tmpDir = await fs.mkdtemp( - path.resolve(os.tmpdir(), 'electron-spec-preload-') - ); - dir = tmpDir; - await fs.writeFile(path.resolve(tmpDir, 'preload.js'), preloadContent); - w = new BrowserWindow({ - show: false, - webPreferences: { - contextIsolation: true, - nodeIntegration: true, - sandbox: useSandbox, - preload: path.resolve(tmpDir, 'preload.js'), - additionalArguments: [ - '--unsafely-expose-electron-internals-for-testing' - ] - } - }); - await w.loadURL( - `http://127.0.0.1:${(server.address() as AddressInfo).port}` - ); - }; - const callWithBindings = (fn: Function) => - w.webContents.executeJavaScript(`(${fn.toString()})(window)`); - describe('overrideGlobalValueFromIsolatedWorld', () => { - it('should override top level properties', async () => { - await makeBindingWindow(() => { - contextBridge.internalContextBridge!.overrideGlobalValueFromIsolatedWorld( - ['open'], - () => ({ you: 'are a wizard' }) - ); - }); - const result = await callWithBindings(async (root: any) => { - return root.open(); - }); - expect(result).to.deep.equal({ you: 'are a wizard' }); - }); - - it('should override deep properties', async () => { - await makeBindingWindow(() => { - contextBridge.internalContextBridge!.overrideGlobalValueFromIsolatedWorld( - ['document', 'foo'], - () => 'I am foo' - ); - }); - const result = await callWithBindings(async (root: any) => { - return root.document.foo(); - }); - expect(result).to.equal('I am foo'); - }); - }); - - describe('overrideGlobalPropertyFromIsolatedWorld', () => { - it('should call the getter correctly', async () => { - await makeBindingWindow(() => { - let callCount = 0; - const getter = () => { - callCount++; - return true; - }; - contextBridge.internalContextBridge!.overrideGlobalPropertyFromIsolatedWorld( - ['isFun'], - getter - ); - contextBridge.exposeInMainWorld('foo', { - callCount: () => callCount - }); - }); - const result = await callWithBindings(async (root: any) => { - return [root.isFun, root.foo.callCount()]; - }); - expect(result[0]).to.equal(true); - expect(result[1]).to.equal(1); - }); - - it('should not make a setter if none is provided', async () => { - await makeBindingWindow(() => { - contextBridge.internalContextBridge!.overrideGlobalPropertyFromIsolatedWorld( - ['isFun'], - () => true - ); - }); - const result = await callWithBindings(async (root: any) => { - root.isFun = 123; - return root.isFun; - }); - expect(result).to.equal(true); - }); - - it('should call the setter correctly', async () => { - await makeBindingWindow(() => { - const callArgs: any[] = []; - const setter = (...args: any[]) => { - callArgs.push(args); - return true; - }; - contextBridge.internalContextBridge!.overrideGlobalPropertyFromIsolatedWorld( - ['isFun'], - () => true, - setter - ); - contextBridge.exposeInMainWorld('foo', { - callArgs: () => callArgs - }); - }); - const result = await callWithBindings(async (root: any) => { - root.isFun = 123; - return root.foo.callArgs(); - }); - expect(result).to.have.lengthOf(1); - expect(result[0]).to.have.lengthOf(1); - expect(result[0][0]).to.equal(123); + expect(result.protoMatches).to.deep.equal(result.protoMatches.map(() => true)); }); }); + }; - describe('overrideGlobalValueWithDynamicPropsFromIsolatedWorld', () => { - it('should not affect normal values', async () => { - await makeBindingWindow(() => { - contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld( - ['thing'], - { - a: 123, - b: () => 2, - c: () => ({ d: 3 }) - } - ); - }); - const result = await callWithBindings(async (root: any) => { - return [root.thing.a, root.thing.b(), root.thing.c()]; - }); - expect(result).to.deep.equal([123, 2, { d: 3 }]); - }); - - it('should work with getters', async () => { - await makeBindingWindow(() => { - contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld( - ['thing'], - { - get foo () { - return 'hi there'; - } - } - ); - }); - const result = await callWithBindings(async (root: any) => { - return root.thing.foo; - }); - expect(result).to.equal('hi there'); - }); - - it('should work with nested getters', async () => { - await makeBindingWindow(() => { - contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld( - ['thing'], - { - get foo () { - return { - get bar () { - return 'hi there'; - } - }; - } - } - ); - }); - const result = await callWithBindings(async (root: any) => { - return root.thing.foo.bar; - }); - expect(result).to.equal('hi there'); - }); - - it('should work with setters', async () => { - await makeBindingWindow(() => { - let a: any = null; - contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld( - ['thing'], - { - get foo () { - return a; - }, - set foo (arg: any) { - a = arg + 1; - } - } - ); - }); - const result = await callWithBindings(async (root: any) => { - root.thing.foo = 123; - return root.thing.foo; - }); - expect(result).to.equal(124); - }); - - it('should work with nested getter / setter combos', async () => { - await makeBindingWindow(() => { - let a: any = null; - contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld( - ['thing'], - { - get thingy () { - return { - get foo () { - return a; - }, - set foo (arg: any) { - a = arg + 1; - } - }; - } - } - ); - }); - const result = await callWithBindings(async (root: any) => { - root.thing.thingy.foo = 123; - return root.thing.thingy.foo; - }); - expect(result).to.equal(124); - }); - - it('should work with deep properties', async () => { - await makeBindingWindow(() => { - contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld( - ['thing'], - { - a: () => ({ - get foo () { - return 'still here'; - } - }) - } - ); - }); - const result = await callWithBindings(async (root: any) => { - return root.thing.a().foo; - }); - expect(result).to.equal('still here'); - }); - }); - }); - }; - - generateTests(true); - generateTests(false); + generateTests(true); + generateTests(false); + }); }); describe('ContextBridgeMutability', () => { it('should not make properties unwriteable and read-only if ContextBridgeMutability is on', async () => { const appPath = path.join(fixturesPath, 'context-bridge-mutability'); - const appProcess = cp.spawn(process.execPath, [ - '--enable-logging', - '--enable-features=ContextBridgeMutability', - appPath - ]); + const appProcess = cp.spawn(process.execPath, ['--enable-logging', '--enable-features=ContextBridgeMutability', appPath]); let output = ''; - appProcess.stdout.on('data', (data) => { - output += data; - }); + appProcess.stdout.on('data', data => { output += data; }); await emittedOnce(appProcess, 'exit'); expect(output).to.include('some-modified-text'); @@ -2593,15 +2221,10 @@ describe('ContextBridgeMutability', () => { it('should make properties unwriteable and read-only if ContextBridgeMutability is off', async () => { const appPath = path.join(fixturesPath, 'context-bridge-mutability'); - const appProcess = cp.spawn(process.execPath, [ - '--enable-logging', - appPath - ]); + const appProcess = cp.spawn(process.execPath, ['--enable-logging', appPath]); let output = ''; - appProcess.stdout.on('data', (data) => { - output += data; - }); + appProcess.stdout.on('data', data => { output += data; }); await emittedOnce(appProcess, 'exit'); expect(output).to.include('some-text');