api-context-bridge-spec.ts 51 KB

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