webview-spec.ts 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707
  1. import * as path from 'path';
  2. import { BrowserWindow, session, ipcMain, app, WebContents } from 'electron/main';
  3. import { closeAllWindows } from './window-helpers';
  4. import { emittedOnce } from './events-helpers';
  5. import { ifdescribe } from './spec-helpers';
  6. import { expect } from 'chai';
  7. const features = process.electronBinding('features');
  8. async function loadWebView (w: WebContents, attributes: Record<string, string>, openDevTools: boolean = false): Promise<void> {
  9. await w.executeJavaScript(`
  10. new Promise((resolve, reject) => {
  11. const webview = new WebView()
  12. for (const [k, v] of Object.entries(${JSON.stringify(attributes)})) {
  13. webview.setAttribute(k, v)
  14. }
  15. document.body.appendChild(webview)
  16. webview.addEventListener('dom-ready', () => {
  17. if (${openDevTools}) {
  18. webview.openDevTools()
  19. }
  20. })
  21. webview.addEventListener('did-finish-load', () => {
  22. resolve()
  23. })
  24. })
  25. `);
  26. }
  27. describe('<webview> tag', function () {
  28. const fixtures = path.join(__dirname, '..', 'spec', 'fixtures');
  29. afterEach(closeAllWindows);
  30. function hideChildWindows (e: any, wc: WebContents) {
  31. wc.on('new-window', (event, url, frameName, disposition, options) => {
  32. options.show = false;
  33. });
  34. }
  35. before(() => {
  36. app.on('web-contents-created', hideChildWindows);
  37. });
  38. after(() => {
  39. app.off('web-contents-created', hideChildWindows);
  40. });
  41. it('works without script tag in page', async () => {
  42. const w = new BrowserWindow({
  43. show: false,
  44. webPreferences: {
  45. webviewTag: true,
  46. nodeIntegration: true
  47. }
  48. });
  49. w.loadFile(path.join(fixtures, 'pages', 'webview-no-script.html'));
  50. await emittedOnce(ipcMain, 'pong');
  51. });
  52. it('works with sandbox', async () => {
  53. const w = new BrowserWindow({
  54. show: false,
  55. webPreferences: {
  56. webviewTag: true,
  57. sandbox: true
  58. }
  59. });
  60. w.loadFile(path.join(fixtures, 'pages', 'webview-isolated.html'));
  61. await emittedOnce(ipcMain, 'pong');
  62. });
  63. it('works with contextIsolation', async () => {
  64. const w = new BrowserWindow({
  65. show: false,
  66. webPreferences: {
  67. webviewTag: true,
  68. contextIsolation: true
  69. }
  70. });
  71. w.loadFile(path.join(fixtures, 'pages', 'webview-isolated.html'));
  72. await emittedOnce(ipcMain, 'pong');
  73. });
  74. it('works with contextIsolation + sandbox', async () => {
  75. const w = new BrowserWindow({
  76. show: false,
  77. webPreferences: {
  78. webviewTag: true,
  79. contextIsolation: true,
  80. sandbox: true
  81. }
  82. });
  83. w.loadFile(path.join(fixtures, 'pages', 'webview-isolated.html'));
  84. await emittedOnce(ipcMain, 'pong');
  85. });
  86. it('works with Trusted Types', async () => {
  87. const w = new BrowserWindow({
  88. show: false,
  89. webPreferences: {
  90. webviewTag: true
  91. }
  92. });
  93. w.loadFile(path.join(fixtures, 'pages', 'webview-trusted-types.html'));
  94. await emittedOnce(ipcMain, 'pong');
  95. });
  96. it('is disabled by default', async () => {
  97. const w = new BrowserWindow({
  98. show: false,
  99. webPreferences: {
  100. preload: path.join(fixtures, 'module', 'preload-webview.js'),
  101. nodeIntegration: true
  102. }
  103. });
  104. const webview = emittedOnce(ipcMain, 'webview');
  105. w.loadFile(path.join(fixtures, 'pages', 'webview-no-script.html'));
  106. const [, type] = await webview;
  107. expect(type).to.equal('undefined', 'WebView still exists');
  108. });
  109. // FIXME(deepak1556): Ch69 follow up.
  110. xdescribe('document.visibilityState/hidden', () => {
  111. afterEach(() => {
  112. ipcMain.removeAllListeners('pong');
  113. });
  114. it('updates when the window is shown after the ready-to-show event', async () => {
  115. const w = new BrowserWindow({ show: false });
  116. const readyToShowSignal = emittedOnce(w, 'ready-to-show');
  117. const pongSignal1 = emittedOnce(ipcMain, 'pong');
  118. w.loadFile(path.join(fixtures, 'pages', 'webview-visibilitychange.html'));
  119. await pongSignal1;
  120. const pongSignal2 = emittedOnce(ipcMain, 'pong');
  121. await readyToShowSignal;
  122. w.show();
  123. const [, visibilityState, hidden] = await pongSignal2;
  124. expect(visibilityState).to.equal('visible');
  125. expect(hidden).to.be.false();
  126. });
  127. it('inherits the parent window visibility state and receives visibilitychange events', async () => {
  128. const w = new BrowserWindow({ show: false });
  129. w.loadFile(path.join(fixtures, 'pages', 'webview-visibilitychange.html'));
  130. const [, visibilityState, hidden] = await emittedOnce(ipcMain, 'pong');
  131. expect(visibilityState).to.equal('hidden');
  132. expect(hidden).to.be.true();
  133. // We have to start waiting for the event
  134. // before we ask the webContents to resize.
  135. const getResponse = emittedOnce(ipcMain, 'pong');
  136. w.webContents.emit('-window-visibility-change', 'visible');
  137. return getResponse.then(([, visibilityState, hidden]) => {
  138. expect(visibilityState).to.equal('visible');
  139. expect(hidden).to.be.false();
  140. });
  141. });
  142. });
  143. describe('did-attach-webview event', () => {
  144. it('is emitted when a webview has been attached', async () => {
  145. const w = new BrowserWindow({
  146. show: false,
  147. webPreferences: {
  148. webviewTag: true,
  149. nodeIntegration: true
  150. }
  151. });
  152. const didAttachWebview = emittedOnce(w.webContents, 'did-attach-webview');
  153. const webviewDomReady = emittedOnce(ipcMain, 'webview-dom-ready');
  154. w.loadFile(path.join(fixtures, 'pages', 'webview-did-attach-event.html'));
  155. const [, webContents] = await didAttachWebview;
  156. const [, id] = await webviewDomReady;
  157. expect(webContents.id).to.equal(id);
  158. });
  159. });
  160. it('loads devtools extensions registered on the parent window', async () => {
  161. const w = new BrowserWindow({
  162. show: false,
  163. webPreferences: {
  164. webviewTag: true,
  165. nodeIntegration: true
  166. }
  167. });
  168. BrowserWindow.removeDevToolsExtension('foo');
  169. const extensionPath = path.join(__dirname, 'fixtures', 'devtools-extensions', 'foo');
  170. await BrowserWindow.addDevToolsExtension(extensionPath);
  171. w.loadFile(path.join(__dirname, 'fixtures', 'pages', 'webview-devtools.html'));
  172. loadWebView(w.webContents, {
  173. nodeintegration: 'on',
  174. src: `file://${path.join(__dirname, 'fixtures', 'blank.html')}`
  175. }, true);
  176. let childWebContentsId = 0;
  177. app.once('web-contents-created', (e, webContents) => {
  178. childWebContentsId = webContents.id;
  179. webContents.on('devtools-opened', function () {
  180. const showPanelIntervalId = setInterval(function () {
  181. if (!webContents.isDestroyed() && webContents.devToolsWebContents) {
  182. webContents.devToolsWebContents.executeJavaScript('(' + function () {
  183. const lastPanelId: any = (window as any).UI.inspectorView._tabbedPane._tabs.peekLast().id;
  184. (window as any).UI.inspectorView.showPanel(lastPanelId);
  185. }.toString() + ')()');
  186. } else {
  187. clearInterval(showPanelIntervalId);
  188. }
  189. }, 100);
  190. });
  191. });
  192. const [, { runtimeId, tabId }] = await emittedOnce(ipcMain, 'answer');
  193. expect(runtimeId).to.match(/^[a-z]{32}$/);
  194. expect(tabId).to.equal(childWebContentsId);
  195. });
  196. describe('zoom behavior', () => {
  197. const zoomScheme = standardScheme;
  198. const webviewSession = session.fromPartition('webview-temp');
  199. before(() => {
  200. const protocol = webviewSession.protocol;
  201. protocol.registerStringProtocol(zoomScheme, (request, callback) => {
  202. callback('hello');
  203. });
  204. });
  205. after(() => {
  206. const protocol = webviewSession.protocol;
  207. protocol.unregisterProtocol(zoomScheme);
  208. });
  209. it('inherits the zoomFactor of the parent window', async () => {
  210. const w = new BrowserWindow({
  211. show: false,
  212. webPreferences: {
  213. webviewTag: true,
  214. nodeIntegration: true,
  215. zoomFactor: 1.2
  216. }
  217. });
  218. const zoomEventPromise = emittedOnce(ipcMain, 'webview-parent-zoom-level');
  219. w.loadFile(path.join(fixtures, 'pages', 'webview-zoom-factor.html'));
  220. const [, zoomFactor, zoomLevel] = await zoomEventPromise;
  221. expect(zoomFactor).to.equal(1.2);
  222. expect(zoomLevel).to.equal(1);
  223. });
  224. it('maintains zoom level on navigation', async () => {
  225. const w = new BrowserWindow({
  226. show: false,
  227. webPreferences: {
  228. webviewTag: true,
  229. nodeIntegration: true,
  230. zoomFactor: 1.2
  231. }
  232. });
  233. const promise = new Promise((resolve) => {
  234. ipcMain.on('webview-zoom-level', (event, zoomLevel, zoomFactor, newHost, final) => {
  235. if (!newHost) {
  236. expect(zoomFactor).to.equal(1.44);
  237. expect(zoomLevel).to.equal(2.0);
  238. } else {
  239. expect(zoomFactor).to.equal(1.2);
  240. expect(zoomLevel).to.equal(1);
  241. }
  242. if (final) {
  243. resolve();
  244. }
  245. });
  246. });
  247. w.loadFile(path.join(fixtures, 'pages', 'webview-custom-zoom-level.html'));
  248. await promise;
  249. });
  250. it('maintains zoom level when navigating within same page', async () => {
  251. const w = new BrowserWindow({
  252. show: false,
  253. webPreferences: {
  254. webviewTag: true,
  255. nodeIntegration: true,
  256. zoomFactor: 1.2
  257. }
  258. });
  259. const promise = new Promise((resolve) => {
  260. ipcMain.on('webview-zoom-in-page', (event, zoomLevel, zoomFactor, final) => {
  261. expect(zoomFactor).to.equal(1.44);
  262. expect(zoomLevel).to.equal(2.0);
  263. if (final) {
  264. resolve();
  265. }
  266. });
  267. });
  268. w.loadFile(path.join(fixtures, 'pages', 'webview-in-page-navigate.html'));
  269. await promise;
  270. });
  271. it('inherits zoom level for the origin when available', async () => {
  272. const w = new BrowserWindow({
  273. show: false,
  274. webPreferences: {
  275. webviewTag: true,
  276. nodeIntegration: true,
  277. zoomFactor: 1.2
  278. }
  279. });
  280. w.loadFile(path.join(fixtures, 'pages', 'webview-origin-zoom-level.html'));
  281. const [, zoomLevel] = await emittedOnce(ipcMain, 'webview-origin-zoom-level');
  282. expect(zoomLevel).to.equal(2.0);
  283. });
  284. it('does not crash when navigating with zoom level inherited from parent', async () => {
  285. const w = new BrowserWindow({
  286. show: false,
  287. webPreferences: {
  288. webviewTag: true,
  289. nodeIntegration: true,
  290. zoomFactor: 1.2,
  291. session: webviewSession
  292. }
  293. });
  294. const attachPromise = emittedOnce(w.webContents, 'did-attach-webview');
  295. const readyPromise = emittedOnce(ipcMain, 'dom-ready');
  296. w.loadFile(path.join(fixtures, 'pages', 'webview-zoom-inherited.html'));
  297. const [, webview] = await attachPromise;
  298. await readyPromise;
  299. expect(webview.getZoomFactor()).to.equal(1.2);
  300. await w.loadURL(`${zoomScheme}://host1`);
  301. });
  302. });
  303. describe('nativeWindowOpen option', () => {
  304. let w: BrowserWindow;
  305. beforeEach(async () => {
  306. w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, webviewTag: true } });
  307. await w.loadURL('about:blank');
  308. });
  309. afterEach(closeAllWindows);
  310. it('opens window of about:blank with cross-scripting enabled', async () => {
  311. // Don't wait for loading to finish.
  312. loadWebView(w.webContents, {
  313. allowpopups: 'on',
  314. nodeintegration: 'on',
  315. webpreferences: 'nativeWindowOpen=1',
  316. src: `file://${path.join(fixtures, 'api', 'native-window-open-blank.html')}`
  317. });
  318. const [, content] = await emittedOnce(ipcMain, 'answer');
  319. expect(content).to.equal('Hello');
  320. });
  321. it('opens window of same domain with cross-scripting enabled', async () => {
  322. // Don't wait for loading to finish.
  323. loadWebView(w.webContents, {
  324. allowpopups: 'on',
  325. nodeintegration: 'on',
  326. webpreferences: 'nativeWindowOpen=1',
  327. src: `file://${path.join(fixtures, 'api', 'native-window-open-file.html')}`
  328. });
  329. const [, content] = await emittedOnce(ipcMain, 'answer');
  330. expect(content).to.equal('Hello');
  331. });
  332. it('returns null from window.open when allowpopups is not set', async () => {
  333. // Don't wait for loading to finish.
  334. loadWebView(w.webContents, {
  335. nodeintegration: 'on',
  336. webpreferences: 'nativeWindowOpen=1',
  337. src: `file://${path.join(fixtures, 'api', 'native-window-open-no-allowpopups.html')}`
  338. });
  339. const [, { windowOpenReturnedNull }] = await emittedOnce(ipcMain, 'answer');
  340. expect(windowOpenReturnedNull).to.be.true();
  341. });
  342. it('blocks accessing cross-origin frames', async () => {
  343. // Don't wait for loading to finish.
  344. loadWebView(w.webContents, {
  345. allowpopups: 'on',
  346. nodeintegration: 'on',
  347. webpreferences: 'nativeWindowOpen=1',
  348. src: `file://${path.join(fixtures, 'api', 'native-window-open-cross-origin.html')}`
  349. });
  350. const [, content] = await emittedOnce(ipcMain, 'answer');
  351. const expectedContent =
  352. 'Blocked a frame with origin "file://" from accessing a cross-origin frame.';
  353. expect(content).to.equal(expectedContent);
  354. });
  355. it('emits a new-window event', async () => {
  356. // Don't wait for loading to finish.
  357. const attributes = {
  358. allowpopups: 'on',
  359. nodeintegration: 'on',
  360. webpreferences: 'nativeWindowOpen=1',
  361. src: `file://${fixtures}/pages/window-open.html`
  362. };
  363. const { url, frameName } = await w.webContents.executeJavaScript(`
  364. new Promise((resolve, reject) => {
  365. const webview = document.createElement('webview')
  366. for (const [k, v] of Object.entries(${JSON.stringify(attributes)})) {
  367. webview.setAttribute(k, v)
  368. }
  369. document.body.appendChild(webview)
  370. webview.addEventListener('new-window', (e) => {
  371. resolve({url: e.url, frameName: e.frameName})
  372. })
  373. })
  374. `);
  375. expect(url).to.equal('http://host/');
  376. expect(frameName).to.equal('host');
  377. });
  378. it('emits a browser-window-created event', async () => {
  379. // Don't wait for loading to finish.
  380. loadWebView(w.webContents, {
  381. allowpopups: 'on',
  382. webpreferences: 'nativeWindowOpen=1',
  383. src: `file://${fixtures}/pages/window-open.html`
  384. });
  385. await emittedOnce(app, 'browser-window-created');
  386. });
  387. it('emits a web-contents-created event', (done) => {
  388. app.on('web-contents-created', function listener (event, contents) {
  389. if (contents.getType() === 'window') {
  390. app.removeListener('web-contents-created', listener);
  391. done();
  392. }
  393. });
  394. loadWebView(w.webContents, {
  395. allowpopups: 'on',
  396. webpreferences: 'nativeWindowOpen=1',
  397. src: `file://${fixtures}/pages/window-open.html`
  398. });
  399. });
  400. });
  401. describe('webpreferences attribute', () => {
  402. let w: BrowserWindow;
  403. beforeEach(async () => {
  404. w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, webviewTag: true } });
  405. await w.loadURL('about:blank');
  406. });
  407. afterEach(closeAllWindows);
  408. it('can enable context isolation', async () => {
  409. loadWebView(w.webContents, {
  410. allowpopups: 'yes',
  411. preload: `file://${fixtures}/api/isolated-preload.js`,
  412. src: `file://${fixtures}/api/isolated.html`,
  413. webpreferences: 'contextIsolation=yes'
  414. });
  415. const [, data] = await emittedOnce(ipcMain, 'isolated-world');
  416. expect(data).to.deep.equal({
  417. preloadContext: {
  418. preloadProperty: 'number',
  419. pageProperty: 'undefined',
  420. typeofRequire: 'function',
  421. typeofProcess: 'object',
  422. typeofArrayPush: 'function',
  423. typeofFunctionApply: 'function',
  424. typeofPreloadExecuteJavaScriptProperty: 'undefined'
  425. },
  426. pageContext: {
  427. preloadProperty: 'undefined',
  428. pageProperty: 'string',
  429. typeofRequire: 'undefined',
  430. typeofProcess: 'undefined',
  431. typeofArrayPush: 'number',
  432. typeofFunctionApply: 'boolean',
  433. typeofPreloadExecuteJavaScriptProperty: 'number',
  434. typeofOpenedWindow: 'object'
  435. }
  436. });
  437. });
  438. });
  439. describe('permission request handlers', () => {
  440. let w: BrowserWindow;
  441. beforeEach(async () => {
  442. w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, webviewTag: true } });
  443. await w.loadURL('about:blank');
  444. });
  445. afterEach(closeAllWindows);
  446. const partition = 'permissionTest';
  447. function setUpRequestHandler (webContentsId: number, requestedPermission: string) {
  448. return new Promise((resolve, reject) => {
  449. session.fromPartition(partition).setPermissionRequestHandler(function (webContents, permission, callback) {
  450. if (webContents.id === webContentsId) {
  451. // requestMIDIAccess with sysex requests both midi and midiSysex so
  452. // grant the first midi one and then reject the midiSysex one
  453. if (requestedPermission === 'midiSysex' && permission === 'midi') {
  454. return callback(true);
  455. }
  456. try {
  457. expect(permission).to.equal(requestedPermission);
  458. } catch (e) {
  459. return reject(e);
  460. }
  461. callback(false);
  462. resolve();
  463. }
  464. });
  465. });
  466. }
  467. afterEach(() => {
  468. session.fromPartition(partition).setPermissionRequestHandler(null);
  469. });
  470. // This is disabled because CI machines don't have cameras or microphones,
  471. // so Chrome responds with "NotFoundError" instead of
  472. // "PermissionDeniedError". It should be re-enabled if we find a way to mock
  473. // the presence of a microphone & camera.
  474. xit('emits when using navigator.getUserMedia api', async () => {
  475. const errorFromRenderer = emittedOnce(ipcMain, 'message');
  476. loadWebView(w.webContents, {
  477. src: `file://${fixtures}/pages/permissions/media.html`,
  478. partition,
  479. nodeintegration: 'on'
  480. });
  481. const [, webViewContents] = await emittedOnce(app, 'web-contents-created');
  482. setUpRequestHandler(webViewContents.id, 'media');
  483. const [, errorName] = await errorFromRenderer;
  484. expect(errorName).to.equal('PermissionDeniedError');
  485. });
  486. it('emits when using navigator.geolocation api', async () => {
  487. const errorFromRenderer = emittedOnce(ipcMain, 'message');
  488. loadWebView(w.webContents, {
  489. src: `file://${fixtures}/pages/permissions/geolocation.html`,
  490. partition,
  491. nodeintegration: 'on'
  492. });
  493. const [, webViewContents] = await emittedOnce(app, 'web-contents-created');
  494. setUpRequestHandler(webViewContents.id, 'geolocation');
  495. const [, error] = await errorFromRenderer;
  496. expect(error).to.equal('User denied Geolocation');
  497. });
  498. it('emits when using navigator.requestMIDIAccess without sysex api', async () => {
  499. const errorFromRenderer = emittedOnce(ipcMain, 'message');
  500. loadWebView(w.webContents, {
  501. src: `file://${fixtures}/pages/permissions/midi.html`,
  502. partition,
  503. nodeintegration: 'on'
  504. });
  505. const [, webViewContents] = await emittedOnce(app, 'web-contents-created');
  506. setUpRequestHandler(webViewContents.id, 'midi');
  507. const [, error] = await errorFromRenderer;
  508. expect(error).to.equal('SecurityError');
  509. });
  510. it('emits when using navigator.requestMIDIAccess with sysex api', async () => {
  511. const errorFromRenderer = emittedOnce(ipcMain, 'message');
  512. loadWebView(w.webContents, {
  513. src: `file://${fixtures}/pages/permissions/midi-sysex.html`,
  514. partition,
  515. nodeintegration: 'on'
  516. });
  517. const [, webViewContents] = await emittedOnce(app, 'web-contents-created');
  518. setUpRequestHandler(webViewContents.id, 'midiSysex');
  519. const [, error] = await errorFromRenderer;
  520. expect(error).to.equal('SecurityError');
  521. });
  522. it('emits when accessing external protocol', async () => {
  523. loadWebView(w.webContents, {
  524. src: 'magnet:test',
  525. partition
  526. });
  527. const [, webViewContents] = await emittedOnce(app, 'web-contents-created');
  528. await setUpRequestHandler(webViewContents.id, 'openExternal');
  529. });
  530. it('emits when using Notification.requestPermission', async () => {
  531. const errorFromRenderer = emittedOnce(ipcMain, 'message');
  532. loadWebView(w.webContents, {
  533. src: `file://${fixtures}/pages/permissions/notification.html`,
  534. partition,
  535. nodeintegration: 'on'
  536. });
  537. const [, webViewContents] = await emittedOnce(app, 'web-contents-created');
  538. await setUpRequestHandler(webViewContents.id, 'notifications');
  539. const [, error] = await errorFromRenderer;
  540. expect(error).to.equal('denied');
  541. });
  542. });
  543. ifdescribe(features.isRemoteModuleEnabled())('enableremotemodule attribute', () => {
  544. let w: BrowserWindow;
  545. beforeEach(async () => {
  546. w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, webviewTag: true } });
  547. await w.loadURL('about:blank');
  548. });
  549. afterEach(closeAllWindows);
  550. const generateSpecs = (description: string, sandbox: boolean) => {
  551. describe(description, () => {
  552. const preload = `file://${fixtures}/module/preload-disable-remote.js`;
  553. const src = `file://${fixtures}/api/blank.html`;
  554. it('enables the remote module by default', async () => {
  555. loadWebView(w.webContents, {
  556. preload,
  557. src,
  558. sandbox: sandbox.toString()
  559. });
  560. const [, webViewContents] = await emittedOnce(app, 'web-contents-created');
  561. const [, , message] = await emittedOnce(webViewContents, 'console-message');
  562. const typeOfRemote = JSON.parse(message);
  563. expect(typeOfRemote).to.equal('object');
  564. });
  565. it('disables the remote module when false', async () => {
  566. loadWebView(w.webContents, {
  567. preload,
  568. src,
  569. sandbox: sandbox.toString(),
  570. enableremotemodule: 'false'
  571. });
  572. const [, webViewContents] = await emittedOnce(app, 'web-contents-created');
  573. const [, , message] = await emittedOnce(webViewContents, 'console-message');
  574. const typeOfRemote = JSON.parse(message);
  575. expect(typeOfRemote).to.equal('undefined');
  576. });
  577. });
  578. };
  579. generateSpecs('without sandbox', false);
  580. generateSpecs('with sandbox', true);
  581. });
  582. describe('DOM events', () => {
  583. afterEach(closeAllWindows);
  584. it('receives extra properties on DOM events when contextIsolation is enabled', async () => {
  585. const w = new BrowserWindow({
  586. show: false,
  587. webPreferences: {
  588. webviewTag: true,
  589. contextIsolation: true
  590. }
  591. });
  592. await w.loadURL('about:blank');
  593. const message = await w.webContents.executeJavaScript(`new Promise((resolve, reject) => {
  594. const webview = new WebView()
  595. webview.setAttribute('src', 'data:text/html,<script>console.log("hi")</script>')
  596. webview.addEventListener('console-message', (e) => {
  597. resolve(e.message)
  598. })
  599. document.body.appendChild(webview)
  600. })`);
  601. expect(message).to.equal('hi');
  602. });
  603. it('emits focus event when contextIsolation is enabled', async () => {
  604. const w = new BrowserWindow({
  605. show: false,
  606. webPreferences: {
  607. webviewTag: true,
  608. contextIsolation: true
  609. }
  610. });
  611. await w.loadURL('about:blank');
  612. await w.webContents.executeJavaScript(`new Promise((resolve, reject) => {
  613. const webview = new WebView()
  614. webview.setAttribute('src', 'about:blank')
  615. webview.addEventListener('dom-ready', () => {
  616. webview.focus()
  617. })
  618. webview.addEventListener('focus', () => {
  619. resolve();
  620. })
  621. document.body.appendChild(webview)
  622. })`);
  623. });
  624. });
  625. });