api-context-bridge-spec.ts 48 KB

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