api-context-bridge-spec.ts 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812
  1. import { BrowserWindow, ipcMain } from 'electron/main';
  2. import { contextBridge } from 'electron/renderer';
  3. import { expect } from 'chai';
  4. import * as fs from 'fs-extra';
  5. import * as http from 'http';
  6. import * as os from 'os';
  7. import * as path from 'path';
  8. import { closeWindow } from './window-helpers';
  9. import { emittedOnce } from './events-helpers';
  10. import { AddressInfo } from 'net';
  11. const fixturesPath = path.resolve(__dirname, 'fixtures', 'api', 'context-bridge');
  12. describe('contextBridge', () => {
  13. let w: BrowserWindow;
  14. let dir: string;
  15. let server: http.Server;
  16. before(async () => {
  17. server = http.createServer((req, res) => {
  18. res.setHeader('Content-Type', 'text/html');
  19. res.end('');
  20. });
  21. await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
  22. });
  23. after(async () => {
  24. if (server) await new Promise(resolve => server.close(resolve));
  25. server = null as any;
  26. });
  27. afterEach(async () => {
  28. await closeWindow(w);
  29. if (dir) await fs.remove(dir);
  30. });
  31. it('should not be accessible when contextIsolation is disabled', async () => {
  32. w = new BrowserWindow({
  33. show: false,
  34. webPreferences: {
  35. contextIsolation: false,
  36. preload: path.resolve(fixturesPath, 'can-bind-preload.js')
  37. }
  38. });
  39. const [, bound] = await emittedOnce(ipcMain, 'context-bridge-bound', () => w.loadFile(path.resolve(fixturesPath, 'empty.html')));
  40. expect(bound).to.equal(false);
  41. });
  42. it('should be accessible when contextIsolation is enabled', async () => {
  43. w = new BrowserWindow({
  44. show: false,
  45. webPreferences: {
  46. contextIsolation: true,
  47. preload: path.resolve(fixturesPath, 'can-bind-preload.js')
  48. }
  49. });
  50. const [, bound] = await emittedOnce(ipcMain, 'context-bridge-bound', () => w.loadFile(path.resolve(fixturesPath, 'empty.html')));
  51. expect(bound).to.equal(true);
  52. });
  53. const generateTests = (useSandbox: boolean) => {
  54. describe(`with sandbox=${useSandbox}`, () => {
  55. const makeBindingWindow = async (bindingCreator: Function) => {
  56. const preloadContent = `const renderer_1 = require('electron');
  57. ${useSandbox ? '' : `require('v8').setFlagsFromString('--expose_gc');
  58. const gc=require('vm').runInNewContext('gc');
  59. renderer_1.contextBridge.exposeInMainWorld('GCRunner', {
  60. run: () => gc()
  61. });`}
  62. (${bindingCreator.toString()})();`;
  63. const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-spec-preload-'));
  64. dir = tmpDir;
  65. await fs.writeFile(path.resolve(tmpDir, 'preload.js'), preloadContent);
  66. w = new BrowserWindow({
  67. show: false,
  68. webPreferences: {
  69. contextIsolation: true,
  70. nodeIntegration: true,
  71. sandbox: useSandbox,
  72. preload: path.resolve(tmpDir, 'preload.js'),
  73. additionalArguments: ['--unsafely-expose-electron-internals-for-testing']
  74. }
  75. });
  76. await w.loadURL(`http://127.0.0.1:${(server.address() as AddressInfo).port}`);
  77. };
  78. const callWithBindings = (fn: Function) =>
  79. w.webContents.executeJavaScript(`(${fn.toString()})(window)`);
  80. const getGCInfo = async (): Promise<{
  81. trackedValues: number;
  82. }> => {
  83. const [, info] = await emittedOnce(ipcMain, 'gc-info', () => w.webContents.send('get-gc-info'));
  84. return info;
  85. };
  86. const forceGCOnWindow = async () => {
  87. w.webContents.debugger.attach();
  88. await w.webContents.debugger.sendCommand('HeapProfiler.enable');
  89. await w.webContents.debugger.sendCommand('HeapProfiler.collectGarbage');
  90. await w.webContents.debugger.sendCommand('HeapProfiler.disable');
  91. w.webContents.debugger.detach();
  92. };
  93. it('should proxy numbers', async () => {
  94. await makeBindingWindow(() => {
  95. contextBridge.exposeInMainWorld('example', {
  96. myNumber: 123
  97. });
  98. });
  99. const result = await callWithBindings((root: any) => {
  100. return root.example.myNumber;
  101. });
  102. expect(result).to.equal(123);
  103. });
  104. it('should make properties unwriteable', async () => {
  105. await makeBindingWindow(() => {
  106. contextBridge.exposeInMainWorld('example', {
  107. myNumber: 123
  108. });
  109. });
  110. const result = await callWithBindings((root: any) => {
  111. root.example.myNumber = 456;
  112. return root.example.myNumber;
  113. });
  114. expect(result).to.equal(123);
  115. });
  116. it('should proxy strings', async () => {
  117. await makeBindingWindow(() => {
  118. contextBridge.exposeInMainWorld('example', {
  119. myString: 'my-words'
  120. });
  121. });
  122. const result = await callWithBindings((root: any) => {
  123. return root.example.myString;
  124. });
  125. expect(result).to.equal('my-words');
  126. });
  127. it('should proxy arrays', async () => {
  128. await makeBindingWindow(() => {
  129. contextBridge.exposeInMainWorld('example', {
  130. myArr: [123, 'my-words']
  131. });
  132. });
  133. const result = await callWithBindings((root: any) => {
  134. return root.example.myArr;
  135. });
  136. expect(result).to.deep.equal([123, 'my-words']);
  137. });
  138. it('should make arrays immutable', async () => {
  139. await makeBindingWindow(() => {
  140. contextBridge.exposeInMainWorld('example', {
  141. myArr: [123, 'my-words']
  142. });
  143. });
  144. const immutable = await callWithBindings((root: any) => {
  145. try {
  146. root.example.myArr.push(456);
  147. return false;
  148. } catch {
  149. return true;
  150. }
  151. });
  152. expect(immutable).to.equal(true);
  153. });
  154. it('should proxy booleans', async () => {
  155. await makeBindingWindow(() => {
  156. contextBridge.exposeInMainWorld('example', {
  157. myBool: true
  158. });
  159. });
  160. const result = await callWithBindings((root: any) => {
  161. return root.example.myBool;
  162. });
  163. expect(result).to.equal(true);
  164. });
  165. it('should proxy promises and resolve with the correct value', async () => {
  166. await makeBindingWindow(() => {
  167. contextBridge.exposeInMainWorld('example', {
  168. myPromise: Promise.resolve('i-resolved')
  169. });
  170. });
  171. const result = await callWithBindings((root: any) => {
  172. return root.example.myPromise;
  173. });
  174. expect(result).to.equal('i-resolved');
  175. });
  176. it('should proxy promises and reject with the correct value', async () => {
  177. await makeBindingWindow(() => {
  178. contextBridge.exposeInMainWorld('example', {
  179. myPromise: Promise.reject(new Error('i-rejected'))
  180. });
  181. });
  182. const result = await callWithBindings(async (root: any) => {
  183. try {
  184. await root.example.myPromise;
  185. return null;
  186. } catch (err) {
  187. return err;
  188. }
  189. });
  190. expect(result).to.be.an.instanceOf(Error).with.property('message', 'Uncaught Error: i-rejected');
  191. });
  192. it('should proxy promises and resolve with the correct value if it resolves later', async () => {
  193. await makeBindingWindow(() => {
  194. contextBridge.exposeInMainWorld('example', {
  195. myPromise: () => new Promise(resolve => setTimeout(() => resolve('delayed'), 20))
  196. });
  197. });
  198. const result = await callWithBindings((root: any) => {
  199. return root.example.myPromise();
  200. });
  201. expect(result).to.equal('delayed');
  202. });
  203. it('should proxy nested promises correctly', async () => {
  204. await makeBindingWindow(() => {
  205. contextBridge.exposeInMainWorld('example', {
  206. myPromise: () => new Promise(resolve => setTimeout(() => resolve(Promise.resolve(123)), 20))
  207. });
  208. });
  209. const result = await callWithBindings((root: any) => {
  210. return root.example.myPromise();
  211. });
  212. expect(result).to.equal(123);
  213. });
  214. it('should proxy methods', async () => {
  215. await makeBindingWindow(() => {
  216. contextBridge.exposeInMainWorld('example', {
  217. getNumber: () => 123,
  218. getString: () => 'help',
  219. getBoolean: () => false,
  220. getPromise: async () => 'promise'
  221. });
  222. });
  223. const result = await callWithBindings(async (root: any) => {
  224. return [root.example.getNumber(), root.example.getString(), root.example.getBoolean(), await root.example.getPromise()];
  225. });
  226. expect(result).to.deep.equal([123, 'help', false, 'promise']);
  227. });
  228. it('should proxy methods that are callable multiple times', async () => {
  229. await makeBindingWindow(() => {
  230. contextBridge.exposeInMainWorld('example', {
  231. doThing: () => 123
  232. });
  233. });
  234. const result = await callWithBindings(async (root: any) => {
  235. return [root.example.doThing(), root.example.doThing(), root.example.doThing()];
  236. });
  237. expect(result).to.deep.equal([123, 123, 123]);
  238. });
  239. it('should proxy methods in the reverse direction', async () => {
  240. await makeBindingWindow(() => {
  241. contextBridge.exposeInMainWorld('example', {
  242. callWithNumber: (fn: any) => fn(123)
  243. });
  244. });
  245. const result = await callWithBindings(async (root: any) => {
  246. return root.example.callWithNumber((n: number) => n + 1);
  247. });
  248. expect(result).to.equal(124);
  249. });
  250. it('should proxy promises in the reverse direction', async () => {
  251. await makeBindingWindow(() => {
  252. contextBridge.exposeInMainWorld('example', {
  253. getPromiseValue: (p: Promise<any>) => p
  254. });
  255. });
  256. const result = await callWithBindings((root: any) => {
  257. return root.example.getPromiseValue(Promise.resolve('my-proxied-value'));
  258. });
  259. expect(result).to.equal('my-proxied-value');
  260. });
  261. it('should proxy objects with number keys', async () => {
  262. await makeBindingWindow(() => {
  263. contextBridge.exposeInMainWorld('example', {
  264. 1: 123,
  265. 2: 456,
  266. 3: 789
  267. });
  268. });
  269. const result = await callWithBindings(async (root: any) => {
  270. return [root.example[1], root.example[2], root.example[3], Array.isArray(root.example)];
  271. });
  272. expect(result).to.deep.equal([123, 456, 789, false]);
  273. });
  274. it('it should proxy null and undefined correctly', async () => {
  275. await makeBindingWindow(() => {
  276. contextBridge.exposeInMainWorld('example', {
  277. values: [null, undefined]
  278. });
  279. });
  280. const result = await callWithBindings((root: any) => {
  281. // Convert to strings as although the context bridge keeps the right value
  282. // IPC does not
  283. return root.example.values.map((val: any) => `${val}`);
  284. });
  285. expect(result).to.deep.equal(['null', 'undefined']);
  286. });
  287. it('should proxy symbols such that symbol equality works', async () => {
  288. await makeBindingWindow(() => {
  289. const mySymbol = Symbol('unique');
  290. contextBridge.exposeInMainWorld('example', {
  291. getSymbol: () => mySymbol,
  292. isSymbol: (s: Symbol) => s === mySymbol
  293. });
  294. });
  295. const result = await callWithBindings((root: any) => {
  296. return root.example.isSymbol(root.example.getSymbol());
  297. });
  298. expect(result).to.equal(true, 'symbols should be equal across contexts');
  299. });
  300. it('should proxy typed arrays and regexps through the serializer', async () => {
  301. await makeBindingWindow(() => {
  302. contextBridge.exposeInMainWorld('example', {
  303. arr: new Uint8Array(100),
  304. regexp: /a/g
  305. });
  306. });
  307. const result = await callWithBindings((root: any) => {
  308. return [
  309. Object.getPrototypeOf(root.example.arr) === Uint8Array.prototype,
  310. Object.getPrototypeOf(root.example.regexp) === RegExp.prototype
  311. ];
  312. });
  313. expect(result).to.deep.equal([true, true]);
  314. });
  315. it('it should handle recursive objects', async () => {
  316. await makeBindingWindow(() => {
  317. const o: any = { value: 135 };
  318. o.o = o;
  319. contextBridge.exposeInMainWorld('example', {
  320. o
  321. });
  322. });
  323. const result = await callWithBindings((root: any) => {
  324. return [root.example.o.value, root.example.o.o.value, root.example.o.o.o.value];
  325. });
  326. expect(result).to.deep.equal([135, 135, 135]);
  327. });
  328. // Can only run tests which use the GCRunner in non-sandboxed environments
  329. if (!useSandbox) {
  330. it('should release the global hold on methods sent across contexts', async () => {
  331. await makeBindingWindow(() => {
  332. require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', { trackedValues: process.electronBinding('v8_util').getWeaklyTrackedValues().length }));
  333. const { weaklyTrackValue } = process.electronBinding('v8_util');
  334. contextBridge.exposeInMainWorld('example', {
  335. getFunction: () => () => 123,
  336. track: weaklyTrackValue
  337. });
  338. });
  339. await callWithBindings(async (root: any) => {
  340. root.GCRunner.run();
  341. });
  342. expect((await getGCInfo()).trackedValues).to.equal(0);
  343. await callWithBindings(async (root: any) => {
  344. const fn = root.example.getFunction();
  345. root.example.track(fn);
  346. root.x = [fn];
  347. });
  348. expect((await getGCInfo()).trackedValues).to.equal(1);
  349. await callWithBindings(async (root: any) => {
  350. root.x = [];
  351. root.GCRunner.run();
  352. });
  353. expect((await getGCInfo()).trackedValues).to.equal(0);
  354. });
  355. }
  356. if (useSandbox) {
  357. it('should not leak the global hold on methods sent across contexts when reloading a sandboxed renderer', async () => {
  358. await makeBindingWindow(() => {
  359. require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', { trackedValues: process.electronBinding('v8_util').getWeaklyTrackedValues().length }));
  360. const { weaklyTrackValue } = process.electronBinding('v8_util');
  361. contextBridge.exposeInMainWorld('example', {
  362. getFunction: () => () => 123,
  363. track: weaklyTrackValue
  364. });
  365. require('electron').ipcRenderer.send('window-ready-for-tasking');
  366. });
  367. const loadPromise = emittedOnce(ipcMain, 'window-ready-for-tasking');
  368. expect((await getGCInfo()).trackedValues).to.equal(0);
  369. await callWithBindings((root: any) => {
  370. root.example.track(root.example.getFunction());
  371. });
  372. expect((await getGCInfo()).trackedValues).to.equal(1);
  373. await callWithBindings((root: any) => {
  374. root.location.reload();
  375. });
  376. await loadPromise;
  377. await forceGCOnWindow();
  378. // If this is ever "2" it means we leaked the exposed function and
  379. // therefore the entire context after a reload
  380. expect((await getGCInfo()).trackedValues).to.equal(0);
  381. });
  382. }
  383. it('it should not let you overwrite existing exposed things', async () => {
  384. await makeBindingWindow(() => {
  385. let threw = false;
  386. contextBridge.exposeInMainWorld('example', {
  387. attempt: 1,
  388. getThrew: () => threw
  389. });
  390. try {
  391. contextBridge.exposeInMainWorld('example', {
  392. attempt: 2,
  393. getThrew: () => threw
  394. });
  395. } catch {
  396. threw = true;
  397. }
  398. });
  399. const result = await callWithBindings((root: any) => {
  400. return [root.example.attempt, root.example.getThrew()];
  401. });
  402. expect(result).to.deep.equal([1, true]);
  403. });
  404. it('should work with complex nested methods and promises', async () => {
  405. await makeBindingWindow(() => {
  406. contextBridge.exposeInMainWorld('example', {
  407. first: (second: Function) => second((fourth: Function) => {
  408. return fourth();
  409. })
  410. });
  411. });
  412. const result = await callWithBindings((root: any) => {
  413. return root.example.first((third: Function) => {
  414. return third(() => Promise.resolve('final value'));
  415. });
  416. });
  417. expect(result).to.equal('final value');
  418. });
  419. it('should throw an error when recursion depth is exceeded', async () => {
  420. await makeBindingWindow(() => {
  421. contextBridge.exposeInMainWorld('example', {
  422. doThing: (a: any) => console.log(a)
  423. });
  424. });
  425. let threw = await callWithBindings((root: any) => {
  426. try {
  427. let a: any = [];
  428. for (let i = 0; i < 999; i++) {
  429. a = [a];
  430. }
  431. root.example.doThing(a);
  432. return false;
  433. } catch {
  434. return true;
  435. }
  436. });
  437. expect(threw).to.equal(false);
  438. threw = await callWithBindings((root: any) => {
  439. try {
  440. let a: any = [];
  441. for (let i = 0; i < 1000; i++) {
  442. a = [a];
  443. }
  444. root.example.doThing(a);
  445. return false;
  446. } catch {
  447. return true;
  448. }
  449. });
  450. expect(threw).to.equal(true);
  451. });
  452. it('should copy thrown errors into the other context', async () => {
  453. await makeBindingWindow(() => {
  454. contextBridge.exposeInMainWorld('example', {
  455. throwNormal: () => {
  456. throw new Error('whoops');
  457. },
  458. throwWeird: () => {
  459. throw 'this is no error...'; // eslint-disable-line no-throw-literal
  460. },
  461. throwNotClonable: () => {
  462. return Object(Symbol('foo'));
  463. },
  464. argumentConvert: () => {}
  465. });
  466. });
  467. const result = await callWithBindings((root: any) => {
  468. const getError = (fn: Function) => {
  469. try {
  470. fn();
  471. } catch (e) {
  472. return e;
  473. }
  474. return null;
  475. };
  476. const normalIsError = Object.getPrototypeOf(getError(root.example.throwNormal)) === Error.prototype;
  477. const weirdIsError = Object.getPrototypeOf(getError(root.example.throwWeird)) === Error.prototype;
  478. const notClonableIsError = Object.getPrototypeOf(getError(root.example.throwNotClonable)) === Error.prototype;
  479. const argumentConvertIsError = Object.getPrototypeOf(getError(() => root.example.argumentConvert(Object(Symbol('test'))))) === Error.prototype;
  480. return [normalIsError, weirdIsError, notClonableIsError, argumentConvertIsError];
  481. });
  482. expect(result).to.deep.equal([true, true, true, true], 'should all be errors in the current context');
  483. });
  484. it('should not leak prototypes', async () => {
  485. await makeBindingWindow(() => {
  486. contextBridge.exposeInMainWorld('example', {
  487. number: 123,
  488. string: 'string',
  489. boolean: true,
  490. arr: [123, 'string', true, ['foo']],
  491. symbol: Symbol('foo'),
  492. bigInt: 10n,
  493. getObject: () => ({ thing: 123 }),
  494. getNumber: () => 123,
  495. getString: () => 'string',
  496. getBoolean: () => true,
  497. getArr: () => [123, 'string', true, ['foo']],
  498. getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']] }),
  499. getFunctionFromFunction: async () => () => null,
  500. object: {
  501. number: 123,
  502. string: 'string',
  503. boolean: true,
  504. arr: [123, 'string', true, ['foo']],
  505. getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']] })
  506. },
  507. receiveArguments: (fn: any) => fn({ key: 'value' })
  508. });
  509. });
  510. const result = await callWithBindings(async (root: any) => {
  511. const { example } = root;
  512. let arg: any;
  513. example.receiveArguments((o: any) => { arg = o; });
  514. const protoChecks = [
  515. [example, Object],
  516. [example.number, Number],
  517. [example.string, String],
  518. [example.boolean, Boolean],
  519. [example.arr, Array],
  520. [example.arr[0], Number],
  521. [example.arr[1], String],
  522. [example.arr[2], Boolean],
  523. [example.arr[3], Array],
  524. [example.arr[3][0], String],
  525. [example.symbol, Symbol],
  526. [example.bigInt, BigInt],
  527. [example.getNumber, Function],
  528. [example.getNumber(), Number],
  529. [example.getObject(), Object],
  530. [example.getString(), String],
  531. [example.getBoolean(), Boolean],
  532. [example.getArr(), Array],
  533. [example.getArr()[0], Number],
  534. [example.getArr()[1], String],
  535. [example.getArr()[2], Boolean],
  536. [example.getArr()[3], Array],
  537. [example.getArr()[3][0], String],
  538. [example.getFunctionFromFunction, Function],
  539. [example.getFunctionFromFunction(), Promise],
  540. [await example.getFunctionFromFunction(), Function],
  541. [example.getPromise(), Promise],
  542. [await example.getPromise(), Object],
  543. [(await example.getPromise()).number, Number],
  544. [(await example.getPromise()).string, String],
  545. [(await example.getPromise()).boolean, Boolean],
  546. [(await example.getPromise()).fn, Function],
  547. [(await example.getPromise()).fn(), String],
  548. [(await example.getPromise()).arr, Array],
  549. [(await example.getPromise()).arr[0], Number],
  550. [(await example.getPromise()).arr[1], String],
  551. [(await example.getPromise()).arr[2], Boolean],
  552. [(await example.getPromise()).arr[3], Array],
  553. [(await example.getPromise()).arr[3][0], String],
  554. [example.object, Object],
  555. [example.object.number, Number],
  556. [example.object.string, String],
  557. [example.object.boolean, Boolean],
  558. [example.object.arr, Array],
  559. [example.object.arr[0], Number],
  560. [example.object.arr[1], String],
  561. [example.object.arr[2], Boolean],
  562. [example.object.arr[3], Array],
  563. [example.object.arr[3][0], String],
  564. [await example.object.getPromise(), Object],
  565. [(await example.object.getPromise()).number, Number],
  566. [(await example.object.getPromise()).string, String],
  567. [(await example.object.getPromise()).boolean, Boolean],
  568. [(await example.object.getPromise()).fn, Function],
  569. [(await example.object.getPromise()).fn(), String],
  570. [(await example.object.getPromise()).arr, Array],
  571. [(await example.object.getPromise()).arr[0], Number],
  572. [(await example.object.getPromise()).arr[1], String],
  573. [(await example.object.getPromise()).arr[2], Boolean],
  574. [(await example.object.getPromise()).arr[3], Array],
  575. [(await example.object.getPromise()).arr[3][0], String],
  576. [arg, Object],
  577. [arg.key, String]
  578. ];
  579. return {
  580. protoMatches: protoChecks.map(([a, Constructor]) => Object.getPrototypeOf(a) === Constructor.prototype)
  581. };
  582. });
  583. // Every protomatch should be true
  584. expect(result.protoMatches).to.deep.equal(result.protoMatches.map(() => true));
  585. });
  586. describe('internalContextBridge', () => {
  587. describe('overrideGlobalValueFromIsolatedWorld', () => {
  588. it('should override top level properties', async () => {
  589. await makeBindingWindow(() => {
  590. contextBridge.internalContextBridge.overrideGlobalValueFromIsolatedWorld(['open'], () => ({ you: 'are a wizard' }));
  591. });
  592. const result = await callWithBindings(async (root: any) => {
  593. return root.open();
  594. });
  595. expect(result).to.deep.equal({ you: 'are a wizard' });
  596. });
  597. it('should override deep properties', async () => {
  598. await makeBindingWindow(() => {
  599. contextBridge.internalContextBridge.overrideGlobalValueFromIsolatedWorld(['document', 'foo'], () => 'I am foo');
  600. });
  601. const result = await callWithBindings(async (root: any) => {
  602. return root.document.foo();
  603. });
  604. expect(result).to.equal('I am foo');
  605. });
  606. });
  607. describe('overrideGlobalPropertyFromIsolatedWorld', () => {
  608. it('should call the getter correctly', async () => {
  609. await makeBindingWindow(() => {
  610. let callCount = 0;
  611. const getter = () => {
  612. callCount++;
  613. return true;
  614. };
  615. contextBridge.internalContextBridge.overrideGlobalPropertyFromIsolatedWorld(['isFun'], getter);
  616. contextBridge.exposeInMainWorld('foo', {
  617. callCount: () => callCount
  618. });
  619. });
  620. const result = await callWithBindings(async (root: any) => {
  621. return [root.isFun, root.foo.callCount()];
  622. });
  623. expect(result[0]).to.equal(true);
  624. expect(result[1]).to.equal(1);
  625. });
  626. it('should not make a setter if none is provided', async () => {
  627. await makeBindingWindow(() => {
  628. contextBridge.internalContextBridge.overrideGlobalPropertyFromIsolatedWorld(['isFun'], () => true);
  629. });
  630. const result = await callWithBindings(async (root: any) => {
  631. root.isFun = 123;
  632. return root.isFun;
  633. });
  634. expect(result).to.equal(true);
  635. });
  636. it('should call the setter correctly', async () => {
  637. await makeBindingWindow(() => {
  638. const callArgs: any[] = [];
  639. const setter = (...args: any[]) => {
  640. callArgs.push(args);
  641. return true;
  642. };
  643. contextBridge.internalContextBridge.overrideGlobalPropertyFromIsolatedWorld(['isFun'], () => true, setter);
  644. contextBridge.exposeInMainWorld('foo', {
  645. callArgs: () => callArgs
  646. });
  647. });
  648. const result = await callWithBindings(async (root: any) => {
  649. root.isFun = 123;
  650. return root.foo.callArgs();
  651. });
  652. expect(result).to.have.lengthOf(1);
  653. expect(result[0]).to.have.lengthOf(1);
  654. expect(result[0][0]).to.equal(123);
  655. });
  656. });
  657. describe('overrideGlobalValueWithDynamicPropsFromIsolatedWorld', () => {
  658. it('should not affect normal values', async () => {
  659. await makeBindingWindow(() => {
  660. contextBridge.internalContextBridge.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['thing'], {
  661. a: 123,
  662. b: () => 2,
  663. c: () => ({ d: 3 })
  664. });
  665. });
  666. const result = await callWithBindings(async (root: any) => {
  667. return [root.thing.a, root.thing.b(), root.thing.c()];
  668. });
  669. expect(result).to.deep.equal([123, 2, { d: 3 }]);
  670. });
  671. it('should work with getters', async () => {
  672. await makeBindingWindow(() => {
  673. contextBridge.internalContextBridge.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['thing'], {
  674. get foo () {
  675. return 'hi there';
  676. }
  677. });
  678. });
  679. const result = await callWithBindings(async (root: any) => {
  680. return root.thing.foo;
  681. });
  682. expect(result).to.equal('hi there');
  683. });
  684. it('should work with nested getters', async () => {
  685. await makeBindingWindow(() => {
  686. contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['thing'], {
  687. get foo () {
  688. return {
  689. get bar () {
  690. return 'hi there';
  691. }
  692. };
  693. }
  694. });
  695. });
  696. const result = await callWithBindings(async (root: any) => {
  697. return root.thing.foo.bar;
  698. });
  699. expect(result).to.equal('hi there');
  700. });
  701. it('should work with setters', async () => {
  702. await makeBindingWindow(() => {
  703. let a: any = null;
  704. contextBridge.internalContextBridge.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['thing'], {
  705. get foo () {
  706. return a;
  707. },
  708. set foo (arg: any) {
  709. a = arg + 1;
  710. }
  711. });
  712. });
  713. const result = await callWithBindings(async (root: any) => {
  714. root.thing.foo = 123;
  715. return root.thing.foo;
  716. });
  717. expect(result).to.equal(124);
  718. });
  719. it('should work with nested getter / setter combos', async () => {
  720. await makeBindingWindow(() => {
  721. let a: any = null;
  722. contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['thing'], {
  723. get thingy () {
  724. return {
  725. get foo () {
  726. return a;
  727. },
  728. set foo (arg: any) {
  729. a = arg + 1;
  730. }
  731. };
  732. }
  733. });
  734. });
  735. const result = await callWithBindings(async (root: any) => {
  736. root.thing.thingy.foo = 123;
  737. return root.thing.thingy.foo;
  738. });
  739. expect(result).to.equal(124);
  740. });
  741. it('should work with deep properties', async () => {
  742. await makeBindingWindow(() => {
  743. contextBridge.internalContextBridge.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['thing'], {
  744. a: () => ({
  745. get foo () {
  746. return 'still here';
  747. }
  748. })
  749. });
  750. });
  751. const result = await callWithBindings(async (root: any) => {
  752. return root.thing.a().foo;
  753. });
  754. expect(result).to.equal('still here');
  755. });
  756. });
  757. });
  758. });
  759. };
  760. generateTests(true);
  761. generateTests(false);
  762. });