api-context-bridge-spec.ts 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320
  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 * as cp from 'child_process';
  9. import { closeWindow } from './lib/window-helpers';
  10. import { listen } from './lib/spec-helpers';
  11. import { once } from 'events';
  12. const fixturesPath = path.resolve(__dirname, 'fixtures', 'api', 'context-bridge');
  13. describe('contextBridge', () => {
  14. let w: BrowserWindow;
  15. let dir: string;
  16. let server: http.Server;
  17. let serverUrl: string;
  18. before(async () => {
  19. server = http.createServer((req, res) => {
  20. res.setHeader('Content-Type', 'text/html');
  21. res.end('');
  22. });
  23. serverUrl = (await listen(server)).url;
  24. });
  25. after(async () => {
  26. if (server) await new Promise(resolve => server.close(resolve));
  27. server = null as any;
  28. });
  29. afterEach(async () => {
  30. await closeWindow(w);
  31. if (dir) await fs.remove(dir);
  32. });
  33. it('should not be accessible when contextIsolation is disabled', async () => {
  34. w = new BrowserWindow({
  35. show: false,
  36. webPreferences: {
  37. contextIsolation: false,
  38. preload: path.resolve(fixturesPath, 'can-bind-preload.js')
  39. }
  40. });
  41. w.loadFile(path.resolve(fixturesPath, 'empty.html'));
  42. const [, bound] = await once(ipcMain, 'context-bridge-bound');
  43. expect(bound).to.equal(false);
  44. });
  45. it('should be accessible when contextIsolation is enabled', async () => {
  46. w = new BrowserWindow({
  47. show: false,
  48. webPreferences: {
  49. contextIsolation: true,
  50. preload: path.resolve(fixturesPath, 'can-bind-preload.js')
  51. }
  52. });
  53. w.loadFile(path.resolve(fixturesPath, 'empty.html'));
  54. const [, bound] = await once(ipcMain, 'context-bridge-bound');
  55. expect(bound).to.equal(true);
  56. });
  57. const generateTests = (useSandbox: boolean) => {
  58. describe(`with sandbox=${useSandbox}`, () => {
  59. const makeBindingWindow = async (bindingCreator: Function, worldId: number = 0) => {
  60. const preloadContentForMainWorld = `const renderer_1 = require('electron');
  61. ${useSandbox ? '' : `require('v8').setFlagsFromString('--expose_gc');
  62. const gc=require('vm').runInNewContext('gc');
  63. renderer_1.contextBridge.exposeInMainWorld('GCRunner', {
  64. run: () => gc()
  65. });`}
  66. (${bindingCreator.toString()})();`;
  67. const preloadContentForIsolatedWorld = `const renderer_1 = require('electron');
  68. ${useSandbox ? '' : `require('v8').setFlagsFromString('--expose_gc');
  69. const gc=require('vm').runInNewContext('gc');
  70. renderer_1.webFrame.setIsolatedWorldInfo(${worldId}, {
  71. name: "Isolated World"
  72. });
  73. renderer_1.contextBridge.exposeInIsolatedWorld(${worldId}, 'GCRunner', {
  74. run: () => gc()
  75. });`}
  76. (${bindingCreator.toString()})();`;
  77. const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-spec-preload-'));
  78. dir = tmpDir;
  79. await fs.writeFile(path.resolve(tmpDir, 'preload.js'), worldId === 0 ? preloadContentForMainWorld : preloadContentForIsolatedWorld);
  80. w = new BrowserWindow({
  81. show: false,
  82. webPreferences: {
  83. contextIsolation: true,
  84. nodeIntegration: true,
  85. sandbox: useSandbox,
  86. preload: path.resolve(tmpDir, 'preload.js'),
  87. additionalArguments: ['--unsafely-expose-electron-internals-for-testing']
  88. }
  89. });
  90. await w.loadURL(serverUrl);
  91. };
  92. const callWithBindings = (fn: Function, worldId: number = 0) =>
  93. worldId === 0 ? w.webContents.executeJavaScript(`(${fn.toString()})(window)`) : w.webContents.executeJavaScriptInIsolatedWorld(worldId, [{ code: `(${fn.toString()})(window)` }]); ;
  94. const getGCInfo = async (): Promise<{
  95. trackedValues: number;
  96. }> => {
  97. w.webContents.send('get-gc-info');
  98. const [, info] = await once(ipcMain, 'gc-info');
  99. return info;
  100. };
  101. const forceGCOnWindow = async () => {
  102. w.webContents.debugger.attach();
  103. await w.webContents.debugger.sendCommand('HeapProfiler.enable');
  104. await w.webContents.debugger.sendCommand('HeapProfiler.collectGarbage');
  105. await w.webContents.debugger.sendCommand('HeapProfiler.disable');
  106. w.webContents.debugger.detach();
  107. };
  108. it('should proxy numbers', async () => {
  109. await makeBindingWindow(() => {
  110. contextBridge.exposeInMainWorld('example', 123);
  111. });
  112. const result = await callWithBindings((root: any) => {
  113. return root.example;
  114. });
  115. expect(result).to.equal(123);
  116. });
  117. it('should proxy numbers when exposed in isolated world', async () => {
  118. await makeBindingWindow(() => {
  119. contextBridge.exposeInIsolatedWorld(1004, 'example', 123);
  120. }, 1004);
  121. const result = await callWithBindings((root: any) => {
  122. return root.example;
  123. }, 1004);
  124. expect(result).to.equal(123);
  125. });
  126. it('should make global properties read-only', async () => {
  127. await makeBindingWindow(() => {
  128. contextBridge.exposeInMainWorld('example', 123);
  129. });
  130. const result = await callWithBindings((root: any) => {
  131. root.example = 456;
  132. return root.example;
  133. });
  134. expect(result).to.equal(123);
  135. });
  136. it('should proxy nested numbers', async () => {
  137. await makeBindingWindow(() => {
  138. contextBridge.exposeInMainWorld('example', {
  139. myNumber: 123
  140. });
  141. });
  142. const result = await callWithBindings((root: any) => {
  143. return root.example.myNumber;
  144. });
  145. expect(result).to.equal(123);
  146. });
  147. it('should make properties unwriteable', async () => {
  148. await makeBindingWindow(() => {
  149. contextBridge.exposeInMainWorld('example', {
  150. myNumber: 123
  151. });
  152. });
  153. const result = await callWithBindings((root: any) => {
  154. root.example.myNumber = 456;
  155. return root.example.myNumber;
  156. });
  157. expect(result).to.equal(123);
  158. });
  159. it('should proxy strings', async () => {
  160. await makeBindingWindow(() => {
  161. contextBridge.exposeInMainWorld('example', 'my-words');
  162. });
  163. const result = await callWithBindings((root: any) => {
  164. return root.example;
  165. });
  166. expect(result).to.equal('my-words');
  167. });
  168. it('should proxy nested strings', async () => {
  169. await makeBindingWindow(() => {
  170. contextBridge.exposeInMainWorld('example', {
  171. myString: 'my-words'
  172. });
  173. });
  174. const result = await callWithBindings((root: any) => {
  175. return root.example.myString;
  176. });
  177. expect(result).to.equal('my-words');
  178. });
  179. it('should proxy nested strings when exposed in isolated world', async () => {
  180. await makeBindingWindow(() => {
  181. contextBridge.exposeInIsolatedWorld(1004, 'example', {
  182. myString: 'my-words'
  183. });
  184. }, 1004);
  185. const result = await callWithBindings((root: any) => {
  186. return root.example.myString;
  187. }, 1004);
  188. expect(result).to.equal('my-words');
  189. });
  190. it('should proxy arrays', async () => {
  191. await makeBindingWindow(() => {
  192. contextBridge.exposeInMainWorld('example', [123, 'my-words']);
  193. });
  194. const result = await callWithBindings((root: any) => {
  195. return [root.example, Array.isArray(root.example)];
  196. });
  197. expect(result).to.deep.equal([[123, 'my-words'], true]);
  198. });
  199. it('should proxy nested arrays', async () => {
  200. await makeBindingWindow(() => {
  201. contextBridge.exposeInMainWorld('example', {
  202. myArr: [123, 'my-words']
  203. });
  204. });
  205. const result = await callWithBindings((root: any) => {
  206. return root.example.myArr;
  207. });
  208. expect(result).to.deep.equal([123, 'my-words']);
  209. });
  210. it('should make arrays immutable', async () => {
  211. await makeBindingWindow(() => {
  212. contextBridge.exposeInMainWorld('example', [123, 'my-words']);
  213. });
  214. const immutable = await callWithBindings((root: any) => {
  215. try {
  216. root.example.push(456);
  217. return false;
  218. } catch {
  219. return true;
  220. }
  221. });
  222. expect(immutable).to.equal(true);
  223. });
  224. it('should make nested arrays immutable', async () => {
  225. await makeBindingWindow(() => {
  226. contextBridge.exposeInMainWorld('example', {
  227. myArr: [123, 'my-words']
  228. });
  229. });
  230. const immutable = await callWithBindings((root: any) => {
  231. try {
  232. root.example.myArr.push(456);
  233. return false;
  234. } catch {
  235. return true;
  236. }
  237. });
  238. expect(immutable).to.equal(true);
  239. });
  240. it('should proxy booleans', async () => {
  241. await makeBindingWindow(() => {
  242. contextBridge.exposeInMainWorld('example', true);
  243. });
  244. const result = await callWithBindings((root: any) => {
  245. return root.example;
  246. });
  247. expect(result).to.equal(true);
  248. });
  249. it('should proxy nested booleans', async () => {
  250. await makeBindingWindow(() => {
  251. contextBridge.exposeInMainWorld('example', {
  252. myBool: true
  253. });
  254. });
  255. const result = await callWithBindings((root: any) => {
  256. return root.example.myBool;
  257. });
  258. expect(result).to.equal(true);
  259. });
  260. it('should proxy promises and resolve with the correct value', async () => {
  261. await makeBindingWindow(() => {
  262. contextBridge.exposeInMainWorld('example',
  263. Promise.resolve('i-resolved')
  264. );
  265. });
  266. const result = await callWithBindings((root: any) => {
  267. return root.example;
  268. });
  269. expect(result).to.equal('i-resolved');
  270. });
  271. it('should proxy nested promises and resolve with the correct value', async () => {
  272. await makeBindingWindow(() => {
  273. contextBridge.exposeInMainWorld('example', {
  274. myPromise: Promise.resolve('i-resolved')
  275. });
  276. });
  277. const result = await callWithBindings((root: any) => {
  278. return root.example.myPromise;
  279. });
  280. expect(result).to.equal('i-resolved');
  281. });
  282. it('should proxy promises and reject with the correct value', async () => {
  283. await makeBindingWindow(() => {
  284. contextBridge.exposeInMainWorld('example', Promise.reject(new Error('i-rejected')));
  285. });
  286. const result = await callWithBindings(async (root: any) => {
  287. try {
  288. await root.example;
  289. return null;
  290. } catch (err) {
  291. return err;
  292. }
  293. });
  294. expect(result).to.be.an.instanceOf(Error).with.property('message', 'i-rejected');
  295. });
  296. it('should proxy nested promises and reject with the correct value', async () => {
  297. await makeBindingWindow(() => {
  298. contextBridge.exposeInMainWorld('example', {
  299. myPromise: Promise.reject(new Error('i-rejected'))
  300. });
  301. });
  302. const result = await callWithBindings(async (root: any) => {
  303. try {
  304. await root.example.myPromise;
  305. return null;
  306. } catch (err) {
  307. return err;
  308. }
  309. });
  310. expect(result).to.be.an.instanceOf(Error).with.property('message', 'i-rejected');
  311. });
  312. it('should proxy promises and resolve with the correct value if it resolves later', async () => {
  313. await makeBindingWindow(() => {
  314. contextBridge.exposeInMainWorld('example', {
  315. myPromise: () => new Promise(resolve => setTimeout(() => resolve('delayed'), 20))
  316. });
  317. });
  318. const result = await callWithBindings((root: any) => {
  319. return root.example.myPromise();
  320. });
  321. expect(result).to.equal('delayed');
  322. });
  323. it('should proxy nested promises correctly', async () => {
  324. await makeBindingWindow(() => {
  325. contextBridge.exposeInMainWorld('example', {
  326. myPromise: () => new Promise(resolve => setTimeout(() => resolve(Promise.resolve(123)), 20))
  327. });
  328. });
  329. const result = await callWithBindings((root: any) => {
  330. return root.example.myPromise();
  331. });
  332. expect(result).to.equal(123);
  333. });
  334. it('should proxy methods', async () => {
  335. await makeBindingWindow(() => {
  336. contextBridge.exposeInMainWorld('example', {
  337. getNumber: () => 123,
  338. getString: () => 'help',
  339. getBoolean: () => false,
  340. getPromise: async () => 'promise'
  341. });
  342. });
  343. const result = await callWithBindings(async (root: any) => {
  344. return [root.example.getNumber(), root.example.getString(), root.example.getBoolean(), await root.example.getPromise()];
  345. });
  346. expect(result).to.deep.equal([123, 'help', false, 'promise']);
  347. });
  348. it('should proxy functions', async () => {
  349. await makeBindingWindow(() => {
  350. contextBridge.exposeInMainWorld('example', () => 'return-value');
  351. });
  352. const result = await callWithBindings(async (root: any) => {
  353. return root.example();
  354. });
  355. expect(result).equal('return-value');
  356. });
  357. it('should not double-proxy functions when they are returned to their origin side of the bridge', async () => {
  358. await makeBindingWindow(() => {
  359. contextBridge.exposeInMainWorld('example', (fn: any) => fn);
  360. });
  361. const result = await callWithBindings(async (root: any) => {
  362. const fn = () => null;
  363. return root.example(fn) === fn;
  364. });
  365. expect(result).equal(true);
  366. });
  367. it('should properly handle errors thrown in proxied functions', async () => {
  368. await makeBindingWindow(() => {
  369. contextBridge.exposeInMainWorld('example', () => { throw new Error('oh no'); });
  370. });
  371. const result = await callWithBindings(async (root: any) => {
  372. try {
  373. root.example();
  374. } catch (e) {
  375. return (e as Error).message;
  376. }
  377. });
  378. expect(result).equal('oh no');
  379. });
  380. it('should proxy methods that are callable multiple times', async () => {
  381. await makeBindingWindow(() => {
  382. contextBridge.exposeInMainWorld('example', {
  383. doThing: () => 123
  384. });
  385. });
  386. const result = await callWithBindings(async (root: any) => {
  387. return [root.example.doThing(), root.example.doThing(), root.example.doThing()];
  388. });
  389. expect(result).to.deep.equal([123, 123, 123]);
  390. });
  391. it('should proxy methods in the reverse direction', async () => {
  392. await makeBindingWindow(() => {
  393. contextBridge.exposeInMainWorld('example', {
  394. callWithNumber: (fn: any) => fn(123)
  395. });
  396. });
  397. const result = await callWithBindings(async (root: any) => {
  398. return root.example.callWithNumber((n: number) => n + 1);
  399. });
  400. expect(result).to.equal(124);
  401. });
  402. it('should proxy promises in the reverse direction', async () => {
  403. await makeBindingWindow(() => {
  404. contextBridge.exposeInMainWorld('example', {
  405. getPromiseValue: (p: Promise<any>) => p
  406. });
  407. });
  408. const result = await callWithBindings((root: any) => {
  409. return root.example.getPromiseValue(Promise.resolve('my-proxied-value'));
  410. });
  411. expect(result).to.equal('my-proxied-value');
  412. });
  413. it('should proxy objects with number keys', async () => {
  414. await makeBindingWindow(() => {
  415. contextBridge.exposeInMainWorld('example', {
  416. 1: 123,
  417. 2: 456,
  418. 3: 789
  419. });
  420. });
  421. const result = await callWithBindings(async (root: any) => {
  422. return [root.example[1], root.example[2], root.example[3], Array.isArray(root.example)];
  423. });
  424. expect(result).to.deep.equal([123, 456, 789, false]);
  425. });
  426. it('it should proxy null', async () => {
  427. await makeBindingWindow(() => {
  428. contextBridge.exposeInMainWorld('example', null);
  429. });
  430. const result = await callWithBindings((root: any) => {
  431. // Convert to strings as although the context bridge keeps the right value
  432. // IPC does not
  433. return `${root.example}`;
  434. });
  435. expect(result).to.deep.equal('null');
  436. });
  437. it('it should proxy undefined', async () => {
  438. await makeBindingWindow(() => {
  439. contextBridge.exposeInMainWorld('example', undefined);
  440. });
  441. const result = await callWithBindings((root: any) => {
  442. // Convert to strings as although the context bridge keeps the right value
  443. // IPC does not
  444. return `${root.example}`;
  445. });
  446. expect(result).to.deep.equal('undefined');
  447. });
  448. it('it should proxy nested null and undefined correctly', async () => {
  449. await makeBindingWindow(() => {
  450. contextBridge.exposeInMainWorld('example', {
  451. values: [null, undefined]
  452. });
  453. });
  454. const result = await callWithBindings((root: any) => {
  455. // Convert to strings as although the context bridge keeps the right value
  456. // IPC does not
  457. return root.example.values.map((val: any) => `${val}`);
  458. });
  459. expect(result).to.deep.equal(['null', 'undefined']);
  460. });
  461. it('should proxy symbols', async () => {
  462. await makeBindingWindow(() => {
  463. const mySymbol = Symbol('unique');
  464. const isSymbol = (s: Symbol) => s === mySymbol;
  465. contextBridge.exposeInMainWorld('symbol', mySymbol);
  466. contextBridge.exposeInMainWorld('isSymbol', isSymbol);
  467. });
  468. const result = await callWithBindings((root: any) => {
  469. return root.isSymbol(root.symbol);
  470. });
  471. expect(result).to.equal(true, 'symbols should be equal across contexts');
  472. });
  473. it('should proxy symbols such that symbol equality works', async () => {
  474. await makeBindingWindow(() => {
  475. const mySymbol = Symbol('unique');
  476. contextBridge.exposeInMainWorld('example', {
  477. getSymbol: () => mySymbol,
  478. isSymbol: (s: Symbol) => s === mySymbol
  479. });
  480. });
  481. const result = await callWithBindings((root: any) => {
  482. return root.example.isSymbol(root.example.getSymbol());
  483. });
  484. expect(result).to.equal(true, 'symbols should be equal across contexts');
  485. });
  486. it('should proxy symbols such that symbol key lookup works', async () => {
  487. await makeBindingWindow(() => {
  488. const mySymbol = Symbol('unique');
  489. contextBridge.exposeInMainWorld('example', {
  490. getSymbol: () => mySymbol,
  491. getObject: () => ({ [mySymbol]: 123 })
  492. });
  493. });
  494. const result = await callWithBindings((root: any) => {
  495. return root.example.getObject()[root.example.getSymbol()];
  496. });
  497. expect(result).to.equal(123, 'symbols key lookup should work across contexts');
  498. });
  499. it('should proxy typed arrays', async () => {
  500. await makeBindingWindow(() => {
  501. contextBridge.exposeInMainWorld('example', new Uint8Array(100));
  502. });
  503. const result = await callWithBindings((root: any) => {
  504. return Object.getPrototypeOf(root.example) === Uint8Array.prototype;
  505. });
  506. expect(result).equal(true);
  507. });
  508. it('should proxy regexps', async () => {
  509. await makeBindingWindow(() => {
  510. contextBridge.exposeInMainWorld('example', /a/g);
  511. });
  512. const result = await callWithBindings((root: any) => {
  513. return Object.getPrototypeOf(root.example) === RegExp.prototype;
  514. });
  515. expect(result).equal(true);
  516. });
  517. it('should proxy typed arrays and regexps through the serializer', async () => {
  518. await makeBindingWindow(() => {
  519. contextBridge.exposeInMainWorld('example', {
  520. arr: new Uint8Array(100),
  521. regexp: /a/g
  522. });
  523. });
  524. const result = await callWithBindings((root: any) => {
  525. return [
  526. Object.getPrototypeOf(root.example.arr) === Uint8Array.prototype,
  527. Object.getPrototypeOf(root.example.regexp) === RegExp.prototype
  528. ];
  529. });
  530. expect(result).to.deep.equal([true, true]);
  531. });
  532. it('should handle recursive objects', async () => {
  533. await makeBindingWindow(() => {
  534. const o: any = { value: 135 };
  535. o.o = o;
  536. contextBridge.exposeInMainWorld('example', {
  537. o
  538. });
  539. });
  540. const result = await callWithBindings((root: any) => {
  541. return [root.example.o.value, root.example.o.o.value, root.example.o.o.o.value];
  542. });
  543. expect(result).to.deep.equal([135, 135, 135]);
  544. });
  545. it('should handle DOM elements', async () => {
  546. await makeBindingWindow(() => {
  547. contextBridge.exposeInMainWorld('example', {
  548. getElem: () => document.body
  549. });
  550. });
  551. const result = await callWithBindings((root: any) => {
  552. return [root.example.getElem().tagName, root.example.getElem().constructor.name, typeof root.example.getElem().querySelector];
  553. });
  554. expect(result).to.deep.equal(['BODY', 'HTMLBodyElement', 'function']);
  555. });
  556. it('should handle DOM elements going backwards over the bridge', async () => {
  557. await makeBindingWindow(() => {
  558. contextBridge.exposeInMainWorld('example', {
  559. getElemInfo: (fn: Function) => {
  560. const elem = fn();
  561. return [elem.tagName, elem.constructor.name, typeof elem.querySelector];
  562. }
  563. });
  564. });
  565. const result = await callWithBindings((root: any) => {
  566. return root.example.getElemInfo(() => document.body);
  567. });
  568. expect(result).to.deep.equal(['BODY', 'HTMLBodyElement', 'function']);
  569. });
  570. it('should handle Blobs', async () => {
  571. await makeBindingWindow(() => {
  572. contextBridge.exposeInMainWorld('example', {
  573. getBlob: () => new Blob(['ab', 'cd'])
  574. });
  575. });
  576. const result = await callWithBindings(async (root: any) => {
  577. return [await root.example.getBlob().text()];
  578. });
  579. expect(result).to.deep.equal(['abcd']);
  580. });
  581. it('should handle Blobs going backwards over the bridge', async () => {
  582. await makeBindingWindow(() => {
  583. contextBridge.exposeInMainWorld('example', {
  584. getBlobText: async (fn: Function) => {
  585. const blob = fn();
  586. return [await blob.text()];
  587. }
  588. });
  589. });
  590. const result = await callWithBindings((root: any) => {
  591. return root.example.getBlobText(() => new Blob(['12', '45']));
  592. });
  593. expect(result).to.deep.equal(['1245']);
  594. });
  595. // Can only run tests which use the GCRunner in non-sandboxed environments
  596. if (!useSandbox) {
  597. it('should release the global hold on methods sent across contexts', async () => {
  598. await makeBindingWindow(() => {
  599. const trackedValues: WeakRef<object>[] = [];
  600. require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', { trackedValues: trackedValues.filter(value => value.deref()).length }));
  601. contextBridge.exposeInMainWorld('example', {
  602. getFunction: () => () => 123,
  603. track: (value: object) => { trackedValues.push(new WeakRef(value)); }
  604. });
  605. });
  606. await callWithBindings(async (root: any) => {
  607. root.GCRunner.run();
  608. });
  609. expect((await getGCInfo()).trackedValues).to.equal(0);
  610. await callWithBindings(async (root: any) => {
  611. const fn = root.example.getFunction();
  612. root.example.track(fn);
  613. root.x = [fn];
  614. });
  615. expect((await getGCInfo()).trackedValues).to.equal(1);
  616. await callWithBindings(async (root: any) => {
  617. root.x = [];
  618. root.GCRunner.run();
  619. });
  620. expect((await getGCInfo()).trackedValues).to.equal(0);
  621. });
  622. }
  623. if (useSandbox) {
  624. it('should not leak the global hold on methods sent across contexts when reloading a sandboxed renderer', async () => {
  625. await makeBindingWindow(() => {
  626. const trackedValues: WeakRef<object>[] = [];
  627. require('electron').ipcRenderer.on('get-gc-info', e => e.sender.send('gc-info', { trackedValues: trackedValues.filter(value => value.deref()).length }));
  628. contextBridge.exposeInMainWorld('example', {
  629. getFunction: () => () => 123,
  630. track: (value: object) => { trackedValues.push(new WeakRef(value)); }
  631. });
  632. require('electron').ipcRenderer.send('window-ready-for-tasking');
  633. });
  634. const loadPromise = once(ipcMain, 'window-ready-for-tasking');
  635. expect((await getGCInfo()).trackedValues).to.equal(0);
  636. await callWithBindings((root: any) => {
  637. root.example.track(root.example.getFunction());
  638. });
  639. expect((await getGCInfo()).trackedValues).to.equal(1);
  640. await callWithBindings((root: any) => {
  641. root.location.reload();
  642. });
  643. await loadPromise;
  644. await forceGCOnWindow();
  645. // If this is ever "2" it means we leaked the exposed function and
  646. // therefore the entire context after a reload
  647. expect((await getGCInfo()).trackedValues).to.equal(0);
  648. });
  649. }
  650. it('it should not let you overwrite existing exposed things', async () => {
  651. await makeBindingWindow(() => {
  652. let threw = false;
  653. contextBridge.exposeInMainWorld('example', {
  654. attempt: 1,
  655. getThrew: () => threw
  656. });
  657. try {
  658. contextBridge.exposeInMainWorld('example', {
  659. attempt: 2,
  660. getThrew: () => threw
  661. });
  662. } catch {
  663. threw = true;
  664. }
  665. });
  666. const result = await callWithBindings((root: any) => {
  667. return [root.example.attempt, root.example.getThrew()];
  668. });
  669. expect(result).to.deep.equal([1, true]);
  670. });
  671. it('should work with complex nested methods and promises', async () => {
  672. await makeBindingWindow(() => {
  673. contextBridge.exposeInMainWorld('example', {
  674. first: (second: Function) => second((fourth: Function) => {
  675. return fourth();
  676. })
  677. });
  678. });
  679. const result = await callWithBindings((root: any) => {
  680. return root.example.first((third: Function) => {
  681. return third(() => Promise.resolve('final value'));
  682. });
  683. });
  684. expect(result).to.equal('final value');
  685. });
  686. it('should work with complex nested methods and promises attached directly to the global', async () => {
  687. await makeBindingWindow(() => {
  688. contextBridge.exposeInMainWorld('example',
  689. (second: Function) => second((fourth: Function) => {
  690. return fourth();
  691. })
  692. );
  693. });
  694. const result = await callWithBindings((root: any) => {
  695. return root.example((third: Function) => {
  696. return third(() => Promise.resolve('final value'));
  697. });
  698. });
  699. expect(result).to.equal('final value');
  700. });
  701. it('should throw an error when recursion depth is exceeded', async () => {
  702. await makeBindingWindow(() => {
  703. contextBridge.exposeInMainWorld('example', {
  704. doThing: (a: any) => console.log(a)
  705. });
  706. });
  707. let threw = await callWithBindings((root: any) => {
  708. try {
  709. let a: any = [];
  710. for (let i = 0; i < 999; i++) {
  711. a = [a];
  712. }
  713. root.example.doThing(a);
  714. return false;
  715. } catch {
  716. return true;
  717. }
  718. });
  719. expect(threw).to.equal(false);
  720. threw = await callWithBindings((root: any) => {
  721. try {
  722. let a: any = [];
  723. for (let i = 0; i < 1000; i++) {
  724. a = [a];
  725. }
  726. root.example.doThing(a);
  727. return false;
  728. } catch {
  729. return true;
  730. }
  731. });
  732. expect(threw).to.equal(true);
  733. });
  734. it('should copy thrown errors into the other context', async () => {
  735. await makeBindingWindow(() => {
  736. contextBridge.exposeInMainWorld('example', {
  737. throwNormal: () => {
  738. throw new Error('whoops');
  739. },
  740. throwWeird: () => {
  741. throw 'this is no error...'; // eslint-disable-line no-throw-literal
  742. },
  743. throwNotClonable: () => {
  744. return Object(Symbol('foo'));
  745. },
  746. throwNotClonableNestedArray: () => {
  747. return [Object(Symbol('foo'))];
  748. },
  749. throwNotClonableNestedObject: () => {
  750. return {
  751. bad: Object(Symbol('foo'))
  752. };
  753. },
  754. throwDynamic: () => {
  755. return {
  756. get bad () {
  757. throw new Error('damm');
  758. }
  759. };
  760. },
  761. argumentConvert: () => {},
  762. rejectNotClonable: async () => {
  763. throw Object(Symbol('foo'));
  764. },
  765. resolveNotClonable: async () => Object(Symbol('foo'))
  766. });
  767. });
  768. const result = await callWithBindings(async (root: any) => {
  769. const getError = (fn: Function) => {
  770. try {
  771. fn();
  772. } catch (e) {
  773. return e;
  774. }
  775. return null;
  776. };
  777. const getAsyncError = async (fn: Function) => {
  778. try {
  779. await fn();
  780. } catch (e) {
  781. return e;
  782. }
  783. return null;
  784. };
  785. const normalIsError = Object.getPrototypeOf(getError(root.example.throwNormal)) === Error.prototype;
  786. const weirdIsError = Object.getPrototypeOf(getError(root.example.throwWeird)) === Error.prototype;
  787. const notClonableIsError = Object.getPrototypeOf(getError(root.example.throwNotClonable)) === Error.prototype;
  788. const notClonableNestedArrayIsError = Object.getPrototypeOf(getError(root.example.throwNotClonableNestedArray)) === Error.prototype;
  789. const notClonableNestedObjectIsError = Object.getPrototypeOf(getError(root.example.throwNotClonableNestedObject)) === Error.prototype;
  790. const dynamicIsError = Object.getPrototypeOf(getError(root.example.throwDynamic)) === Error.prototype;
  791. const argumentConvertIsError = Object.getPrototypeOf(getError(() => root.example.argumentConvert(Object(Symbol('test'))))) === Error.prototype;
  792. const rejectNotClonableIsError = Object.getPrototypeOf(await getAsyncError(root.example.rejectNotClonable)) === Error.prototype;
  793. const resolveNotClonableIsError = Object.getPrototypeOf(await getAsyncError(root.example.resolveNotClonable)) === Error.prototype;
  794. return [normalIsError, weirdIsError, notClonableIsError, notClonableNestedArrayIsError, notClonableNestedObjectIsError, dynamicIsError, argumentConvertIsError, rejectNotClonableIsError, resolveNotClonableIsError];
  795. });
  796. expect(result).to.deep.equal([true, true, true, true, true, true, true, true, true], 'should all be errors in the current context');
  797. });
  798. it('should not leak prototypes', async () => {
  799. await makeBindingWindow(() => {
  800. contextBridge.exposeInMainWorld('example', {
  801. number: 123,
  802. string: 'string',
  803. boolean: true,
  804. arr: [123, 'string', true, ['foo']],
  805. symbol: Symbol('foo'),
  806. bigInt: 10n,
  807. getObject: () => ({ thing: 123 }),
  808. getNumber: () => 123,
  809. getString: () => 'string',
  810. getBoolean: () => true,
  811. getArr: () => [123, 'string', true, ['foo']],
  812. getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']] }),
  813. getFunctionFromFunction: async () => () => null,
  814. object: {
  815. number: 123,
  816. string: 'string',
  817. boolean: true,
  818. arr: [123, 'string', true, ['foo']],
  819. getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']] })
  820. },
  821. receiveArguments: (fn: any) => fn({ key: 'value' }),
  822. symbolKeyed: {
  823. [Symbol('foo')]: 123
  824. },
  825. getBody: () => document.body,
  826. getBlob: () => new Blob(['ab', 'cd'])
  827. });
  828. });
  829. const result = await callWithBindings(async (root: any) => {
  830. const { example } = root;
  831. let arg: any;
  832. example.receiveArguments((o: any) => { arg = o; });
  833. const protoChecks = [
  834. ...Object.keys(example).map(key => [key, String]),
  835. ...Object.getOwnPropertySymbols(example.symbolKeyed).map(key => [key, Symbol]),
  836. [example, Object],
  837. [example.number, Number],
  838. [example.string, String],
  839. [example.boolean, Boolean],
  840. [example.arr, Array],
  841. [example.arr[0], Number],
  842. [example.arr[1], String],
  843. [example.arr[2], Boolean],
  844. [example.arr[3], Array],
  845. [example.arr[3][0], String],
  846. [example.symbol, Symbol],
  847. [example.bigInt, BigInt],
  848. [example.getNumber, Function],
  849. [example.getNumber(), Number],
  850. [example.getObject(), Object],
  851. [example.getString(), String],
  852. [example.getBoolean(), Boolean],
  853. [example.getArr(), Array],
  854. [example.getArr()[0], Number],
  855. [example.getArr()[1], String],
  856. [example.getArr()[2], Boolean],
  857. [example.getArr()[3], Array],
  858. [example.getArr()[3][0], String],
  859. [example.getFunctionFromFunction, Function],
  860. [example.getFunctionFromFunction(), Promise],
  861. [await example.getFunctionFromFunction(), Function],
  862. [example.getPromise(), Promise],
  863. [await example.getPromise(), Object],
  864. [(await example.getPromise()).number, Number],
  865. [(await example.getPromise()).string, String],
  866. [(await example.getPromise()).boolean, Boolean],
  867. [(await example.getPromise()).fn, Function],
  868. [(await example.getPromise()).fn(), String],
  869. [(await example.getPromise()).arr, Array],
  870. [(await example.getPromise()).arr[0], Number],
  871. [(await example.getPromise()).arr[1], String],
  872. [(await example.getPromise()).arr[2], Boolean],
  873. [(await example.getPromise()).arr[3], Array],
  874. [(await example.getPromise()).arr[3][0], String],
  875. [example.object, Object],
  876. [example.object.number, Number],
  877. [example.object.string, String],
  878. [example.object.boolean, Boolean],
  879. [example.object.arr, Array],
  880. [example.object.arr[0], Number],
  881. [example.object.arr[1], String],
  882. [example.object.arr[2], Boolean],
  883. [example.object.arr[3], Array],
  884. [example.object.arr[3][0], String],
  885. [await example.object.getPromise(), Object],
  886. [(await example.object.getPromise()).number, Number],
  887. [(await example.object.getPromise()).string, String],
  888. [(await example.object.getPromise()).boolean, Boolean],
  889. [(await example.object.getPromise()).fn, Function],
  890. [(await example.object.getPromise()).fn(), String],
  891. [(await example.object.getPromise()).arr, Array],
  892. [(await example.object.getPromise()).arr[0], Number],
  893. [(await example.object.getPromise()).arr[1], String],
  894. [(await example.object.getPromise()).arr[2], Boolean],
  895. [(await example.object.getPromise()).arr[3], Array],
  896. [(await example.object.getPromise()).arr[3][0], String],
  897. [arg, Object],
  898. [arg.key, String],
  899. [example.getBody(), HTMLBodyElement],
  900. [example.getBlob(), Blob]
  901. ];
  902. return {
  903. protoMatches: protoChecks.map(([a, Constructor]) => Object.getPrototypeOf(a) === Constructor.prototype)
  904. };
  905. });
  906. // Every protomatch should be true
  907. expect(result.protoMatches).to.deep.equal(result.protoMatches.map(() => true));
  908. });
  909. it('should not leak prototypes when attaching directly to the global', async () => {
  910. await makeBindingWindow(() => {
  911. const toExpose = {
  912. number: 123,
  913. string: 'string',
  914. boolean: true,
  915. arr: [123, 'string', true, ['foo']],
  916. symbol: Symbol('foo'),
  917. bigInt: 10n,
  918. getObject: () => ({ thing: 123 }),
  919. getNumber: () => 123,
  920. getString: () => 'string',
  921. getBoolean: () => true,
  922. getArr: () => [123, 'string', true, ['foo']],
  923. getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']] }),
  924. getFunctionFromFunction: async () => () => null,
  925. getError: () => new Error('foo'),
  926. getWeirdError: () => {
  927. const e = new Error('foo');
  928. e.message = { garbage: true } as any;
  929. return e;
  930. },
  931. object: {
  932. number: 123,
  933. string: 'string',
  934. boolean: true,
  935. arr: [123, 'string', true, ['foo']],
  936. getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']] })
  937. },
  938. receiveArguments: (fn: any) => fn({ key: 'value' }),
  939. symbolKeyed: {
  940. [Symbol('foo')]: 123
  941. }
  942. };
  943. const keys: string[] = [];
  944. Object.entries(toExpose).forEach(([key, value]) => {
  945. keys.push(key);
  946. contextBridge.exposeInMainWorld(key, value);
  947. });
  948. contextBridge.exposeInMainWorld('keys', keys);
  949. });
  950. const result = await callWithBindings(async (root: any) => {
  951. const { keys } = root;
  952. const cleanedRoot: any = {};
  953. for (const [key, value] of Object.entries(root)) {
  954. if (keys.includes(key)) {
  955. cleanedRoot[key] = value;
  956. }
  957. }
  958. let arg: any;
  959. cleanedRoot.receiveArguments((o: any) => { arg = o; });
  960. const protoChecks = [
  961. ...Object.keys(cleanedRoot).map(key => [key, String]),
  962. ...Object.getOwnPropertySymbols(cleanedRoot.symbolKeyed).map(key => [key, Symbol]),
  963. [cleanedRoot, Object],
  964. [cleanedRoot.number, Number],
  965. [cleanedRoot.string, String],
  966. [cleanedRoot.boolean, Boolean],
  967. [cleanedRoot.arr, Array],
  968. [cleanedRoot.arr[0], Number],
  969. [cleanedRoot.arr[1], String],
  970. [cleanedRoot.arr[2], Boolean],
  971. [cleanedRoot.arr[3], Array],
  972. [cleanedRoot.arr[3][0], String],
  973. [cleanedRoot.symbol, Symbol],
  974. [cleanedRoot.bigInt, BigInt],
  975. [cleanedRoot.getNumber, Function],
  976. [cleanedRoot.getNumber(), Number],
  977. [cleanedRoot.getObject(), Object],
  978. [cleanedRoot.getString(), String],
  979. [cleanedRoot.getBoolean(), Boolean],
  980. [cleanedRoot.getArr(), Array],
  981. [cleanedRoot.getArr()[0], Number],
  982. [cleanedRoot.getArr()[1], String],
  983. [cleanedRoot.getArr()[2], Boolean],
  984. [cleanedRoot.getArr()[3], Array],
  985. [cleanedRoot.getArr()[3][0], String],
  986. [cleanedRoot.getFunctionFromFunction, Function],
  987. [cleanedRoot.getFunctionFromFunction(), Promise],
  988. [await cleanedRoot.getFunctionFromFunction(), Function],
  989. [cleanedRoot.getError(), Error],
  990. [cleanedRoot.getError().message, String],
  991. [cleanedRoot.getWeirdError(), Error],
  992. [cleanedRoot.getWeirdError().message, String],
  993. [cleanedRoot.getPromise(), Promise],
  994. [await cleanedRoot.getPromise(), Object],
  995. [(await cleanedRoot.getPromise()).number, Number],
  996. [(await cleanedRoot.getPromise()).string, String],
  997. [(await cleanedRoot.getPromise()).boolean, Boolean],
  998. [(await cleanedRoot.getPromise()).fn, Function],
  999. [(await cleanedRoot.getPromise()).fn(), String],
  1000. [(await cleanedRoot.getPromise()).arr, Array],
  1001. [(await cleanedRoot.getPromise()).arr[0], Number],
  1002. [(await cleanedRoot.getPromise()).arr[1], String],
  1003. [(await cleanedRoot.getPromise()).arr[2], Boolean],
  1004. [(await cleanedRoot.getPromise()).arr[3], Array],
  1005. [(await cleanedRoot.getPromise()).arr[3][0], String],
  1006. [cleanedRoot.object, Object],
  1007. [cleanedRoot.object.number, Number],
  1008. [cleanedRoot.object.string, String],
  1009. [cleanedRoot.object.boolean, Boolean],
  1010. [cleanedRoot.object.arr, Array],
  1011. [cleanedRoot.object.arr[0], Number],
  1012. [cleanedRoot.object.arr[1], String],
  1013. [cleanedRoot.object.arr[2], Boolean],
  1014. [cleanedRoot.object.arr[3], Array],
  1015. [cleanedRoot.object.arr[3][0], String],
  1016. [await cleanedRoot.object.getPromise(), Object],
  1017. [(await cleanedRoot.object.getPromise()).number, Number],
  1018. [(await cleanedRoot.object.getPromise()).string, String],
  1019. [(await cleanedRoot.object.getPromise()).boolean, Boolean],
  1020. [(await cleanedRoot.object.getPromise()).fn, Function],
  1021. [(await cleanedRoot.object.getPromise()).fn(), String],
  1022. [(await cleanedRoot.object.getPromise()).arr, Array],
  1023. [(await cleanedRoot.object.getPromise()).arr[0], Number],
  1024. [(await cleanedRoot.object.getPromise()).arr[1], String],
  1025. [(await cleanedRoot.object.getPromise()).arr[2], Boolean],
  1026. [(await cleanedRoot.object.getPromise()).arr[3], Array],
  1027. [(await cleanedRoot.object.getPromise()).arr[3][0], String],
  1028. [arg, Object],
  1029. [arg.key, String]
  1030. ];
  1031. return {
  1032. protoMatches: protoChecks.map(([a, Constructor]) => Object.getPrototypeOf(a) === Constructor.prototype)
  1033. };
  1034. });
  1035. // Every protomatch should be true
  1036. expect(result.protoMatches).to.deep.equal(result.protoMatches.map(() => true));
  1037. });
  1038. describe('internalContextBridge', () => {
  1039. describe('overrideGlobalValueFromIsolatedWorld', () => {
  1040. it('should override top level properties', async () => {
  1041. await makeBindingWindow(() => {
  1042. contextBridge.internalContextBridge!.overrideGlobalValueFromIsolatedWorld(['open'], () => ({ you: 'are a wizard' }));
  1043. });
  1044. const result = await callWithBindings(async (root: any) => {
  1045. return root.open();
  1046. });
  1047. expect(result).to.deep.equal({ you: 'are a wizard' });
  1048. });
  1049. it('should override deep properties', async () => {
  1050. await makeBindingWindow(() => {
  1051. contextBridge.internalContextBridge!.overrideGlobalValueFromIsolatedWorld(['document', 'foo'], () => 'I am foo');
  1052. });
  1053. const result = await callWithBindings(async (root: any) => {
  1054. return root.document.foo();
  1055. });
  1056. expect(result).to.equal('I am foo');
  1057. });
  1058. });
  1059. describe('overrideGlobalPropertyFromIsolatedWorld', () => {
  1060. it('should call the getter correctly', async () => {
  1061. await makeBindingWindow(() => {
  1062. let callCount = 0;
  1063. const getter = () => {
  1064. callCount++;
  1065. return true;
  1066. };
  1067. contextBridge.internalContextBridge!.overrideGlobalPropertyFromIsolatedWorld(['isFun'], getter);
  1068. contextBridge.exposeInMainWorld('foo', {
  1069. callCount: () => callCount
  1070. });
  1071. });
  1072. const result = await callWithBindings(async (root: any) => {
  1073. return [root.isFun, root.foo.callCount()];
  1074. });
  1075. expect(result[0]).to.equal(true);
  1076. expect(result[1]).to.equal(1);
  1077. });
  1078. it('should not make a setter if none is provided', async () => {
  1079. await makeBindingWindow(() => {
  1080. contextBridge.internalContextBridge!.overrideGlobalPropertyFromIsolatedWorld(['isFun'], () => true);
  1081. });
  1082. const result = await callWithBindings(async (root: any) => {
  1083. root.isFun = 123;
  1084. return root.isFun;
  1085. });
  1086. expect(result).to.equal(true);
  1087. });
  1088. it('should call the setter correctly', async () => {
  1089. await makeBindingWindow(() => {
  1090. const callArgs: any[] = [];
  1091. const setter = (...args: any[]) => {
  1092. callArgs.push(args);
  1093. return true;
  1094. };
  1095. contextBridge.internalContextBridge!.overrideGlobalPropertyFromIsolatedWorld(['isFun'], () => true, setter);
  1096. contextBridge.exposeInMainWorld('foo', {
  1097. callArgs: () => callArgs
  1098. });
  1099. });
  1100. const result = await callWithBindings(async (root: any) => {
  1101. root.isFun = 123;
  1102. return root.foo.callArgs();
  1103. });
  1104. expect(result).to.have.lengthOf(1);
  1105. expect(result[0]).to.have.lengthOf(1);
  1106. expect(result[0][0]).to.equal(123);
  1107. });
  1108. });
  1109. describe('overrideGlobalValueWithDynamicPropsFromIsolatedWorld', () => {
  1110. it('should not affect normal values', async () => {
  1111. await makeBindingWindow(() => {
  1112. contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['thing'], {
  1113. a: 123,
  1114. b: () => 2,
  1115. c: () => ({ d: 3 })
  1116. });
  1117. });
  1118. const result = await callWithBindings(async (root: any) => {
  1119. return [root.thing.a, root.thing.b(), root.thing.c()];
  1120. });
  1121. expect(result).to.deep.equal([123, 2, { d: 3 }]);
  1122. });
  1123. it('should work with getters', async () => {
  1124. await makeBindingWindow(() => {
  1125. contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['thing'], {
  1126. get foo () {
  1127. return 'hi there';
  1128. }
  1129. });
  1130. });
  1131. const result = await callWithBindings(async (root: any) => {
  1132. return root.thing.foo;
  1133. });
  1134. expect(result).to.equal('hi there');
  1135. });
  1136. it('should work with nested getters', async () => {
  1137. await makeBindingWindow(() => {
  1138. contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['thing'], {
  1139. get foo () {
  1140. return {
  1141. get bar () {
  1142. return 'hi there';
  1143. }
  1144. };
  1145. }
  1146. });
  1147. });
  1148. const result = await callWithBindings(async (root: any) => {
  1149. return root.thing.foo.bar;
  1150. });
  1151. expect(result).to.equal('hi there');
  1152. });
  1153. it('should work with setters', async () => {
  1154. await makeBindingWindow(() => {
  1155. let a: any = null;
  1156. contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['thing'], {
  1157. get foo () {
  1158. return a;
  1159. },
  1160. set foo (arg: any) {
  1161. a = arg + 1;
  1162. }
  1163. });
  1164. });
  1165. const result = await callWithBindings(async (root: any) => {
  1166. root.thing.foo = 123;
  1167. return root.thing.foo;
  1168. });
  1169. expect(result).to.equal(124);
  1170. });
  1171. it('should work with nested getter / setter combos', async () => {
  1172. await makeBindingWindow(() => {
  1173. let a: any = null;
  1174. contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['thing'], {
  1175. get thingy () {
  1176. return {
  1177. get foo () {
  1178. return a;
  1179. },
  1180. set foo (arg: any) {
  1181. a = arg + 1;
  1182. }
  1183. };
  1184. }
  1185. });
  1186. });
  1187. const result = await callWithBindings(async (root: any) => {
  1188. root.thing.thingy.foo = 123;
  1189. return root.thing.thingy.foo;
  1190. });
  1191. expect(result).to.equal(124);
  1192. });
  1193. it('should work with deep properties', async () => {
  1194. await makeBindingWindow(() => {
  1195. contextBridge.internalContextBridge!.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['thing'], {
  1196. a: () => ({
  1197. get foo () {
  1198. return 'still here';
  1199. }
  1200. })
  1201. });
  1202. });
  1203. const result = await callWithBindings(async (root: any) => {
  1204. return root.thing.a().foo;
  1205. });
  1206. expect(result).to.equal('still here');
  1207. });
  1208. });
  1209. });
  1210. });
  1211. };
  1212. generateTests(true);
  1213. generateTests(false);
  1214. });
  1215. describe('ContextBridgeMutability', () => {
  1216. it('should not make properties unwriteable and read-only if ContextBridgeMutability is on', async () => {
  1217. const appPath = path.join(fixturesPath, 'context-bridge-mutability');
  1218. const appProcess = cp.spawn(process.execPath, ['--enable-logging', '--enable-features=ContextBridgeMutability', appPath]);
  1219. let output = '';
  1220. appProcess.stdout.on('data', data => { output += data; });
  1221. await once(appProcess, 'exit');
  1222. expect(output).to.include('some-modified-text');
  1223. expect(output).to.include('obj-modified-prop');
  1224. expect(output).to.include('1,2,5,3,4');
  1225. });
  1226. it('should make properties unwriteable and read-only if ContextBridgeMutability is off', async () => {
  1227. const appPath = path.join(fixturesPath, 'context-bridge-mutability');
  1228. const appProcess = cp.spawn(process.execPath, ['--enable-logging', appPath]);
  1229. let output = '';
  1230. appProcess.stdout.on('data', data => { output += data; });
  1231. await once(appProcess, 'exit');
  1232. expect(output).to.include('some-text');
  1233. expect(output).to.include('obj-prop');
  1234. expect(output).to.include('1,2,3,4');
  1235. });
  1236. });