api-context-bridge-spec.ts 51 KB

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