api-web-contents-spec.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  1. 'use strict'
  2. const assert = require('assert')
  3. const http = require('http')
  4. const path = require('path')
  5. const {closeWindow} = require('./window-helpers')
  6. const {emittedOnce} = require('./events-helpers')
  7. const {ipcRenderer, remote, clipboard} = require('electron')
  8. const {BrowserWindow, webContents, ipcMain, session} = remote
  9. const isCi = remote.getGlobal('isCi')
  10. /* The whole webContents API doesn't use standard callbacks */
  11. /* eslint-disable standard/no-callback-literal */
  12. describe('webContents module', () => {
  13. const fixtures = path.resolve(__dirname, 'fixtures')
  14. let w
  15. beforeEach(() => {
  16. w = new BrowserWindow({
  17. show: false,
  18. width: 400,
  19. height: 400,
  20. webPreferences: {
  21. backgroundThrottling: false
  22. }
  23. })
  24. })
  25. afterEach(() => closeWindow(w).then(() => { w = null }))
  26. describe('getAllWebContents() API', () => {
  27. it('returns an array of web contents', (done) => {
  28. w.webContents.on('devtools-opened', () => {
  29. const all = webContents.getAllWebContents().sort((a, b) => {
  30. return a.getId() - b.getId()
  31. })
  32. assert.ok(all.length >= 4)
  33. assert.equal(all[0].getType(), 'window')
  34. assert.equal(all[all.length - 2].getType(), 'remote')
  35. assert.equal(all[all.length - 1].getType(), 'webview')
  36. done()
  37. })
  38. w.loadURL(`file://${path.join(fixtures, 'pages', 'webview-zoom-factor.html')}`)
  39. w.webContents.openDevTools()
  40. })
  41. })
  42. describe('getFocusedWebContents() API', () => {
  43. it('returns the focused web contents', (done) => {
  44. if (isCi) return done()
  45. const specWebContents = remote.getCurrentWebContents()
  46. assert.equal(specWebContents.getId(), webContents.getFocusedWebContents().getId())
  47. specWebContents.once('devtools-opened', () => {
  48. assert.equal(specWebContents.devToolsWebContents.getId(), webContents.getFocusedWebContents().getId())
  49. specWebContents.closeDevTools()
  50. })
  51. specWebContents.once('devtools-closed', () => {
  52. assert.equal(specWebContents.getId(), webContents.getFocusedWebContents().getId())
  53. done()
  54. })
  55. specWebContents.openDevTools()
  56. })
  57. it('does not crash when called on a detached dev tools window', (done) => {
  58. const specWebContents = w.webContents
  59. specWebContents.once('devtools-opened', () => {
  60. assert.doesNotThrow(() => {
  61. webContents.getFocusedWebContents()
  62. })
  63. specWebContents.closeDevTools()
  64. })
  65. specWebContents.once('devtools-closed', () => {
  66. assert.doesNotThrow(() => {
  67. webContents.getFocusedWebContents()
  68. })
  69. done()
  70. })
  71. specWebContents.openDevTools({mode: 'detach'})
  72. w.inspectElement(100, 100)
  73. })
  74. })
  75. describe('setDevToolsWebContents() API', () => {
  76. it('sets arbitry webContents as devtools', (done) => {
  77. let devtools = new BrowserWindow({show: false})
  78. devtools.webContents.once('dom-ready', () => {
  79. assert.ok(devtools.getURL().startsWith('chrome-devtools://devtools'))
  80. devtools.webContents.executeJavaScript('InspectorFrontendHost.constructor.name', (name) => {
  81. assert.ok(name, 'InspectorFrontendHostImpl')
  82. devtools.destroy()
  83. done()
  84. })
  85. })
  86. w.webContents.setDevToolsWebContents(devtools.webContents)
  87. w.webContents.openDevTools()
  88. })
  89. })
  90. describe('isFocused() API', () => {
  91. it('returns false when the window is hidden', () => {
  92. BrowserWindow.getAllWindows().forEach((window) => {
  93. assert.equal(!window.isVisible() && window.webContents.isFocused(), false)
  94. })
  95. })
  96. })
  97. describe('getWebPreferences() API', () => {
  98. it('should not crash when called for devTools webContents', (done) => {
  99. w.webContents.openDevTools()
  100. w.webContents.once('devtools-opened', () => {
  101. assert(!w.devToolsWebContents.getWebPreferences())
  102. done()
  103. })
  104. })
  105. })
  106. describe('before-input-event event', () => {
  107. it('can prevent document keyboard events', (done) => {
  108. w.loadURL(`file://${path.join(__dirname, 'fixtures', 'pages', 'key-events.html')}`)
  109. w.webContents.once('did-finish-load', () => {
  110. ipcMain.once('keydown', (event, key) => {
  111. assert.equal(key, 'b')
  112. done()
  113. })
  114. ipcRenderer.send('prevent-next-input-event', 'a', w.webContents.id)
  115. w.webContents.sendInputEvent({type: 'keyDown', keyCode: 'a'})
  116. w.webContents.sendInputEvent({type: 'keyDown', keyCode: 'b'})
  117. })
  118. })
  119. it('has the correct properties', (done) => {
  120. w.loadURL(`file://${path.join(__dirname, 'fixtures', 'pages', 'base-page.html')}`)
  121. w.webContents.once('did-finish-load', () => {
  122. const testBeforeInput = (opts) => {
  123. return new Promise((resolve, reject) => {
  124. w.webContents.once('before-input-event', (event, input) => {
  125. assert.equal(input.type, opts.type)
  126. assert.equal(input.key, opts.key)
  127. assert.equal(input.code, opts.code)
  128. assert.equal(input.isAutoRepeat, opts.isAutoRepeat)
  129. assert.equal(input.shift, opts.shift)
  130. assert.equal(input.control, opts.control)
  131. assert.equal(input.alt, opts.alt)
  132. assert.equal(input.meta, opts.meta)
  133. resolve()
  134. })
  135. const modifiers = []
  136. if (opts.shift) modifiers.push('shift')
  137. if (opts.control) modifiers.push('control')
  138. if (opts.alt) modifiers.push('alt')
  139. if (opts.meta) modifiers.push('meta')
  140. if (opts.isAutoRepeat) modifiers.push('isAutoRepeat')
  141. w.webContents.sendInputEvent({
  142. type: opts.type,
  143. keyCode: opts.keyCode,
  144. modifiers: modifiers
  145. })
  146. })
  147. }
  148. Promise.resolve().then(() => {
  149. return testBeforeInput({
  150. type: 'keyDown',
  151. key: 'A',
  152. code: 'KeyA',
  153. keyCode: 'a',
  154. shift: true,
  155. control: true,
  156. alt: true,
  157. meta: true,
  158. isAutoRepeat: true
  159. })
  160. }).then(() => {
  161. return testBeforeInput({
  162. type: 'keyUp',
  163. key: '.',
  164. code: 'Period',
  165. keyCode: '.',
  166. shift: false,
  167. control: true,
  168. alt: true,
  169. meta: false,
  170. isAutoRepeat: false
  171. })
  172. }).then(() => {
  173. return testBeforeInput({
  174. type: 'keyUp',
  175. key: '!',
  176. code: 'Digit1',
  177. keyCode: '1',
  178. shift: true,
  179. control: false,
  180. alt: false,
  181. meta: true,
  182. isAutoRepeat: false
  183. })
  184. }).then(() => {
  185. return testBeforeInput({
  186. type: 'keyUp',
  187. key: 'Tab',
  188. code: 'Tab',
  189. keyCode: 'Tab',
  190. shift: false,
  191. control: true,
  192. alt: false,
  193. meta: false,
  194. isAutoRepeat: true
  195. })
  196. }).then(done).catch(done)
  197. })
  198. })
  199. })
  200. describe('devtools window', () => {
  201. let testFn = it
  202. if (process.platform === 'darwin' && isCi) {
  203. testFn = it.skip
  204. }
  205. try {
  206. // We have other tests that check if native modules work, if we fail to require
  207. // robotjs let's skip this test to avoid false negatives
  208. require('robotjs')
  209. } catch (err) {
  210. testFn = it.skip
  211. }
  212. testFn('can receive and handle menu events', async function () {
  213. this.timeout(5000)
  214. w.show()
  215. w.loadFile(path.join(fixtures, 'pages', 'key-events.html'))
  216. // Ensure the devtools are loaded
  217. w.webContents.closeDevTools()
  218. const opened = emittedOnce(w.webContents, 'devtools-opened')
  219. w.webContents.openDevTools()
  220. await opened
  221. await emittedOnce(w.webContents.devToolsWebContents, 'did-finish-load')
  222. w.webContents.devToolsWebContents.focus()
  223. // Focus an input field
  224. await w.webContents.devToolsWebContents.executeJavaScript(
  225. `const input = document.createElement('input');
  226. document.body.innerHTML = '';
  227. document.body.appendChild(input)
  228. input.focus();`
  229. )
  230. // Write something to the clipboard
  231. clipboard.writeText('test value')
  232. // Fake a paste request using robotjs to emulate a REAL keyboard paste event
  233. require('robotjs').keyTap('v', process.platform === 'darwin' ? ['command'] : ['control'])
  234. const start = Date.now()
  235. let val
  236. // Check every now and again for the pasted value (paste is async)
  237. while (val !== 'test value' && Date.now() - start <= 1000) {
  238. val = await w.webContents.devToolsWebContents.executeJavaScript(
  239. `document.querySelector('input').value`
  240. )
  241. await new Promise(resolve => setTimeout(resolve, 10))
  242. }
  243. // Once we're done expect the paste to have been successful
  244. assert.equal(val, 'test value', 'value should eventually become the pasted value')
  245. })
  246. })
  247. describe('sendInputEvent(event)', () => {
  248. beforeEach((done) => {
  249. w.loadURL(`file://${path.join(__dirname, 'fixtures', 'pages', 'key-events.html')}`)
  250. w.webContents.once('did-finish-load', () => done())
  251. })
  252. it('can send keydown events', (done) => {
  253. ipcMain.once('keydown', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
  254. assert.equal(key, 'a')
  255. assert.equal(code, 'KeyA')
  256. assert.equal(keyCode, 65)
  257. assert.equal(shiftKey, false)
  258. assert.equal(ctrlKey, false)
  259. assert.equal(altKey, false)
  260. done()
  261. })
  262. w.webContents.sendInputEvent({type: 'keyDown', keyCode: 'A'})
  263. })
  264. it('can send keydown events with modifiers', (done) => {
  265. ipcMain.once('keydown', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
  266. assert.equal(key, 'Z')
  267. assert.equal(code, 'KeyZ')
  268. assert.equal(keyCode, 90)
  269. assert.equal(shiftKey, true)
  270. assert.equal(ctrlKey, true)
  271. assert.equal(altKey, false)
  272. done()
  273. })
  274. w.webContents.sendInputEvent({type: 'keyDown', keyCode: 'Z', modifiers: ['shift', 'ctrl']})
  275. })
  276. it('can send keydown events with special keys', (done) => {
  277. ipcMain.once('keydown', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
  278. assert.equal(key, 'Tab')
  279. assert.equal(code, 'Tab')
  280. assert.equal(keyCode, 9)
  281. assert.equal(shiftKey, false)
  282. assert.equal(ctrlKey, false)
  283. assert.equal(altKey, true)
  284. done()
  285. })
  286. w.webContents.sendInputEvent({type: 'keyDown', keyCode: 'Tab', modifiers: ['alt']})
  287. })
  288. it('can send char events', (done) => {
  289. ipcMain.once('keypress', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
  290. assert.equal(key, 'a')
  291. assert.equal(code, 'KeyA')
  292. assert.equal(keyCode, 65)
  293. assert.equal(shiftKey, false)
  294. assert.equal(ctrlKey, false)
  295. assert.equal(altKey, false)
  296. done()
  297. })
  298. w.webContents.sendInputEvent({type: 'keyDown', keyCode: 'A'})
  299. w.webContents.sendInputEvent({type: 'char', keyCode: 'A'})
  300. })
  301. it('can send char events with modifiers', (done) => {
  302. ipcMain.once('keypress', (event, key, code, keyCode, shiftKey, ctrlKey, altKey) => {
  303. assert.equal(key, 'Z')
  304. assert.equal(code, 'KeyZ')
  305. assert.equal(keyCode, 90)
  306. assert.equal(shiftKey, true)
  307. assert.equal(ctrlKey, true)
  308. assert.equal(altKey, false)
  309. done()
  310. })
  311. w.webContents.sendInputEvent({type: 'keyDown', keyCode: 'Z'})
  312. w.webContents.sendInputEvent({type: 'char', keyCode: 'Z', modifiers: ['shift', 'ctrl']})
  313. })
  314. })
  315. it('supports inserting CSS', (done) => {
  316. w.loadURL('about:blank')
  317. w.webContents.insertCSS('body { background-repeat: round; }')
  318. w.webContents.executeJavaScript('window.getComputedStyle(document.body).getPropertyValue("background-repeat")', (result) => {
  319. assert.equal(result, 'round')
  320. done()
  321. })
  322. })
  323. it('supports inspecting an element in the devtools', (done) => {
  324. w.loadURL('about:blank')
  325. w.webContents.once('devtools-opened', () => {
  326. done()
  327. })
  328. w.webContents.inspectElement(10, 10)
  329. })
  330. describe('startDrag({file, icon})', () => {
  331. it('throws errors for a missing file or a missing/empty icon', () => {
  332. assert.throws(() => {
  333. w.webContents.startDrag({icon: path.join(__dirname, 'fixtures', 'assets', 'logo.png')})
  334. }, /Must specify either 'file' or 'files' option/)
  335. assert.throws(() => {
  336. w.webContents.startDrag({file: __filename})
  337. }, /Must specify 'icon' option/)
  338. if (process.platform === 'darwin') {
  339. assert.throws(() => {
  340. w.webContents.startDrag({file: __filename, icon: __filename})
  341. }, /Must specify non-empty 'icon' option/)
  342. }
  343. })
  344. })
  345. describe('focus()', () => {
  346. describe('when the web contents is hidden', () => {
  347. it('does not blur the focused window', (done) => {
  348. ipcMain.once('answer', (event, parentFocused, childFocused) => {
  349. assert.equal(parentFocused, true)
  350. assert.equal(childFocused, false)
  351. done()
  352. })
  353. w.show()
  354. w.loadURL(`file://${path.join(__dirname, 'fixtures', 'pages', 'focus-web-contents.html')}`)
  355. })
  356. })
  357. })
  358. describe('getOSProcessId()', () => {
  359. it('returns a valid procress id', (done) => {
  360. assert.strictEqual(w.webContents.getOSProcessId(), 0)
  361. w.webContents.once('did-finish-load', () => {
  362. const pid = w.webContents.getOSProcessId()
  363. assert.equal(typeof pid, 'number')
  364. assert(pid > 0, `pid ${pid} is not greater than 0`)
  365. done()
  366. })
  367. w.loadURL('about:blank')
  368. })
  369. })
  370. describe('zoom api', () => {
  371. const zoomScheme = remote.getGlobal('zoomScheme')
  372. const hostZoomMap = {
  373. host1: 0.3,
  374. host2: 0.7,
  375. host3: 0.2
  376. }
  377. before((done) => {
  378. const protocol = session.defaultSession.protocol
  379. protocol.registerStringProtocol(zoomScheme, (request, callback) => {
  380. const response = `<script>
  381. const {ipcRenderer, remote} = require('electron')
  382. ipcRenderer.send('set-zoom', window.location.hostname)
  383. ipcRenderer.on(window.location.hostname + '-zoom-set', () => {
  384. remote.getCurrentWebContents().getZoomLevel((zoomLevel) => {
  385. ipcRenderer.send(window.location.hostname + '-zoom-level', zoomLevel)
  386. })
  387. })
  388. </script>`
  389. callback({data: response, mimeType: 'text/html'})
  390. }, (error) => done(error))
  391. })
  392. after((done) => {
  393. const protocol = session.defaultSession.protocol
  394. protocol.unregisterProtocol(zoomScheme, (error) => done(error))
  395. })
  396. it('can set the correct zoom level', (done) => {
  397. w.loadURL('about:blank')
  398. w.webContents.on('did-finish-load', () => {
  399. w.webContents.getZoomLevel((zoomLevel) => {
  400. assert.equal(zoomLevel, 0.0)
  401. w.webContents.setZoomLevel(0.5)
  402. w.webContents.getZoomLevel((zoomLevel) => {
  403. assert.equal(zoomLevel, 0.5)
  404. w.webContents.setZoomLevel(0)
  405. done()
  406. })
  407. })
  408. })
  409. })
  410. it('can persist zoom level across navigation', (done) => {
  411. let finalNavigation = false
  412. ipcMain.on('set-zoom', (e, host) => {
  413. const zoomLevel = hostZoomMap[host]
  414. if (!finalNavigation) w.webContents.setZoomLevel(zoomLevel)
  415. e.sender.send(`${host}-zoom-set`)
  416. })
  417. ipcMain.on('host1-zoom-level', (e, zoomLevel) => {
  418. const expectedZoomLevel = hostZoomMap.host1
  419. assert.equal(zoomLevel, expectedZoomLevel)
  420. if (finalNavigation) {
  421. done()
  422. } else {
  423. w.loadURL(`${zoomScheme}://host2`)
  424. }
  425. })
  426. ipcMain.once('host2-zoom-level', (e, zoomLevel) => {
  427. const expectedZoomLevel = hostZoomMap.host2
  428. assert.equal(zoomLevel, expectedZoomLevel)
  429. finalNavigation = true
  430. w.webContents.goBack()
  431. })
  432. w.loadURL(`${zoomScheme}://host1`)
  433. })
  434. it('can propagate zoom level across same session', (done) => {
  435. const w2 = new BrowserWindow({
  436. show: false
  437. })
  438. w2.webContents.on('did-finish-load', () => {
  439. w.webContents.getZoomLevel((zoomLevel1) => {
  440. assert.equal(zoomLevel1, hostZoomMap.host3)
  441. w2.webContents.getZoomLevel((zoomLevel2) => {
  442. assert.equal(zoomLevel1, zoomLevel2)
  443. w2.setClosable(true)
  444. w2.close()
  445. done()
  446. })
  447. })
  448. })
  449. w.webContents.on('did-finish-load', () => {
  450. w.webContents.setZoomLevel(hostZoomMap.host3)
  451. w2.loadURL(`${zoomScheme}://host3`)
  452. })
  453. w.loadURL(`${zoomScheme}://host3`)
  454. })
  455. it('cannot propagate zoom level across different session', (done) => {
  456. const w2 = new BrowserWindow({
  457. show: false,
  458. webPreferences: {
  459. partition: 'temp'
  460. }
  461. })
  462. const protocol = w2.webContents.session.protocol
  463. protocol.registerStringProtocol(zoomScheme, (request, callback) => {
  464. callback('hello')
  465. }, (error) => {
  466. if (error) return done(error)
  467. w2.webContents.on('did-finish-load', () => {
  468. w.webContents.getZoomLevel((zoomLevel1) => {
  469. assert.equal(zoomLevel1, hostZoomMap.host3)
  470. w2.webContents.getZoomLevel((zoomLevel2) => {
  471. assert.equal(zoomLevel2, 0)
  472. assert.notEqual(zoomLevel1, zoomLevel2)
  473. protocol.unregisterProtocol(zoomScheme, (error) => {
  474. if (error) return done(error)
  475. w2.setClosable(true)
  476. w2.close()
  477. done()
  478. })
  479. })
  480. })
  481. })
  482. w.webContents.on('did-finish-load', () => {
  483. w.webContents.setZoomLevel(hostZoomMap.host3)
  484. w2.loadURL(`${zoomScheme}://host3`)
  485. })
  486. w.loadURL(`${zoomScheme}://host3`)
  487. })
  488. })
  489. it('can persist when it contains iframe', (done) => {
  490. const server = http.createServer((req, res) => {
  491. setTimeout(() => {
  492. res.end()
  493. }, 200)
  494. })
  495. server.listen(0, '127.0.0.1', () => {
  496. const url = 'http://127.0.0.1:' + server.address().port
  497. const content = `<iframe src=${url}></iframe>`
  498. w.webContents.on('did-frame-finish-load', (e, isMainFrame) => {
  499. if (!isMainFrame) {
  500. w.webContents.getZoomLevel((zoomLevel) => {
  501. assert.equal(zoomLevel, 2.0)
  502. w.webContents.setZoomLevel(0)
  503. server.close()
  504. done()
  505. })
  506. }
  507. })
  508. w.webContents.on('dom-ready', () => {
  509. w.webContents.setZoomLevel(2.0)
  510. })
  511. w.loadURL(`data:text/html,${content}`)
  512. })
  513. })
  514. it('cannot propagate when used with webframe', (done) => {
  515. let finalZoomLevel = 0
  516. const w2 = new BrowserWindow({
  517. show: false
  518. })
  519. w2.webContents.on('did-finish-load', () => {
  520. w.webContents.getZoomLevel((zoomLevel1) => {
  521. assert.equal(zoomLevel1, finalZoomLevel)
  522. w2.webContents.getZoomLevel((zoomLevel2) => {
  523. assert.equal(zoomLevel2, 0)
  524. assert.notEqual(zoomLevel1, zoomLevel2)
  525. w2.setClosable(true)
  526. w2.close()
  527. done()
  528. })
  529. })
  530. })
  531. ipcMain.once('temporary-zoom-set', (e, zoomLevel) => {
  532. w2.loadURL(`file://${fixtures}/pages/c.html`)
  533. finalZoomLevel = zoomLevel
  534. })
  535. w.loadURL(`file://${fixtures}/pages/webframe-zoom.html`)
  536. })
  537. it('cannot persist zoom level after navigation with webFrame', (done) => {
  538. let initialNavigation = true
  539. const source = `
  540. const {ipcRenderer, webFrame} = require('electron')
  541. webFrame.setZoomLevel(0.6)
  542. ipcRenderer.send('zoom-level-set', webFrame.getZoomLevel())
  543. `
  544. w.webContents.on('did-finish-load', () => {
  545. if (initialNavigation) {
  546. w.webContents.executeJavaScript(source, () => {})
  547. } else {
  548. w.webContents.getZoomLevel((zoomLevel) => {
  549. assert.equal(zoomLevel, 0)
  550. done()
  551. })
  552. }
  553. })
  554. ipcMain.once('zoom-level-set', (e, zoomLevel) => {
  555. assert.equal(zoomLevel, 0.6)
  556. w.loadURL(`file://${fixtures}/pages/d.html`)
  557. initialNavigation = false
  558. })
  559. w.loadURL(`file://${fixtures}/pages/c.html`)
  560. })
  561. })
  562. describe('webrtc ip policy api', () => {
  563. it('can set and get webrtc ip policies', () => {
  564. const policies = [
  565. 'default',
  566. 'default_public_interface_only',
  567. 'default_public_and_private_interfaces',
  568. 'disable_non_proxied_udp'
  569. ]
  570. policies.forEach((policy) => {
  571. w.webContents.setWebRTCIPHandlingPolicy(policy)
  572. assert.equal(w.webContents.getWebRTCIPHandlingPolicy(), policy)
  573. })
  574. })
  575. })
  576. describe('will-prevent-unload event', () => {
  577. it('does not emit if beforeunload returns undefined', (done) => {
  578. w.once('closed', () => {
  579. done()
  580. })
  581. w.webContents.on('will-prevent-unload', (e) => {
  582. assert.fail('should not have fired')
  583. })
  584. w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-undefined.html'))
  585. })
  586. it('emits if beforeunload returns false', (done) => {
  587. w.webContents.on('will-prevent-unload', () => {
  588. done()
  589. })
  590. w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html'))
  591. })
  592. it('supports calling preventDefault on will-prevent-unload events', (done) => {
  593. ipcRenderer.send('prevent-next-will-prevent-unload', w.webContents.id)
  594. w.once('closed', () => done())
  595. w.loadURL('file://' + path.join(fixtures, 'api', 'close-beforeunload-false.html'))
  596. })
  597. })
  598. describe('setIgnoreMenuShortcuts(ignore)', () => {
  599. it('does not throw', () => {
  600. assert.equal(w.webContents.setIgnoreMenuShortcuts(true), undefined)
  601. assert.equal(w.webContents.setIgnoreMenuShortcuts(false), undefined)
  602. })
  603. })
  604. // Destroying webContents in its event listener is going to crash when
  605. // Electron is built in Debug mode.
  606. xdescribe('destroy()', () => {
  607. let server
  608. before((done) => {
  609. server = http.createServer((request, response) => {
  610. switch (request.url) {
  611. case '/404':
  612. response.statusCode = '404'
  613. response.end()
  614. break
  615. case '/301':
  616. response.statusCode = '301'
  617. response.setHeader('Location', '/200')
  618. response.end()
  619. break
  620. case '/200':
  621. response.statusCode = '200'
  622. response.end('hello')
  623. break
  624. default:
  625. done('unsupported endpoint')
  626. }
  627. }).listen(0, '127.0.0.1', () => {
  628. server.url = 'http://127.0.0.1:' + server.address().port
  629. done()
  630. })
  631. })
  632. after(() => {
  633. server.close()
  634. server = null
  635. })
  636. it('should not crash when invoked synchronously inside navigation observer', (done) => {
  637. const events = [
  638. { name: 'did-start-loading', url: `${server.url}/200` },
  639. { name: 'dom-ready', url: `${server.url}/200` },
  640. { name: 'did-stop-loading', url: `${server.url}/200` },
  641. { name: 'did-finish-load', url: `${server.url}/200` },
  642. // FIXME: Multiple Emit calls inside an observer assume that object
  643. // will be alive till end of the observer. Synchronous `destroy` api
  644. // violates this contract and crashes.
  645. // { name: 'did-frame-finish-load', url: `${server.url}/200` },
  646. { name: 'did-fail-load', url: `${server.url}/404` }
  647. ]
  648. const responseEvent = 'webcontents-destroyed'
  649. function * genNavigationEvent () {
  650. let eventOptions = null
  651. while ((eventOptions = events.shift()) && events.length) {
  652. eventOptions.responseEvent = responseEvent
  653. ipcRenderer.send('test-webcontents-navigation-observer', eventOptions)
  654. yield 1
  655. }
  656. }
  657. let gen = genNavigationEvent()
  658. ipcRenderer.on(responseEvent, () => {
  659. if (!gen.next().value) done()
  660. })
  661. gen.next()
  662. })
  663. })
  664. describe('did-change-theme-color event', () => {
  665. it('is triggered with correct theme color', (done) => {
  666. let count = 0
  667. w.webContents.on('did-change-theme-color', (e, color) => {
  668. if (count === 0) {
  669. count += 1
  670. assert.equal(color, '#FFEEDD')
  671. w.loadURL(`file://${path.join(__dirname, 'fixtures', 'pages', 'base-page.html')}`)
  672. } else if (count === 1) {
  673. assert.equal(color, null)
  674. done()
  675. }
  676. })
  677. w.loadURL(`file://${path.join(__dirname, 'fixtures', 'pages', 'theme-color.html')}`)
  678. })
  679. })
  680. describe('console-message event', () => {
  681. it('is triggered with correct log message', (done) => {
  682. w.webContents.on('console-message', (e, level, message) => {
  683. // Don't just assert as Chromium might emit other logs that we should ignore.
  684. if (message === 'a') {
  685. done()
  686. }
  687. })
  688. w.loadURL(`file://${fixtures}/pages/a.html`)
  689. })
  690. })
  691. describe('referrer', () => {
  692. it('propagates referrer information to new target=_blank windows', (done) => {
  693. const server = http.createServer((req, res) => {
  694. if (req.url === '/should_have_referrer') {
  695. assert.equal(req.headers.referer, 'http://127.0.0.1:' + server.address().port + '/')
  696. return done()
  697. }
  698. res.end('<a id="a" href="/should_have_referrer" target="_blank">link</a>')
  699. })
  700. server.listen(0, '127.0.0.1', () => {
  701. const url = 'http://127.0.0.1:' + server.address().port + '/'
  702. w.webContents.once('did-finish-load', () => {
  703. w.webContents.once('new-window', (event, newUrl, frameName, disposition, options, features, referrer) => {
  704. assert.equal(referrer.url, url)
  705. assert.equal(referrer.policy, 'no-referrer-when-downgrade')
  706. })
  707. w.webContents.executeJavaScript('a.click()')
  708. })
  709. w.loadURL(url)
  710. })
  711. })
  712. // TODO(jeremy): window.open() in a real browser passes the referrer, but
  713. // our hacked-up window.open() shim doesn't. It should.
  714. xit('propagates referrer information to windows opened with window.open', (done) => {
  715. const server = http.createServer((req, res) => {
  716. if (req.url === '/should_have_referrer') {
  717. assert.equal(req.headers.referer, 'http://127.0.0.1:' + server.address().port + '/')
  718. return done()
  719. }
  720. res.end('')
  721. })
  722. server.listen(0, '127.0.0.1', () => {
  723. const url = 'http://127.0.0.1:' + server.address().port + '/'
  724. w.webContents.once('did-finish-load', () => {
  725. w.webContents.once('new-window', (event, newUrl, frameName, disposition, options, features, referrer) => {
  726. assert.equal(referrer.url, url)
  727. assert.equal(referrer.policy, 'no-referrer-when-downgrade')
  728. })
  729. w.webContents.executeJavaScript('window.open(location.href + "should_have_referrer")')
  730. })
  731. w.loadURL(url)
  732. })
  733. })
  734. })
  735. describe('webframe messages in sandboxed contents', () => {
  736. it('responds to executeJavaScript', (done) => {
  737. w.destroy()
  738. w = new BrowserWindow({
  739. show: false,
  740. webPreferences: {
  741. sandbox: true
  742. }
  743. })
  744. w.webContents.once('did-finish-load', () => {
  745. w.webContents.executeJavaScript('37 + 5', (result) => {
  746. assert.equal(result, 42)
  747. done()
  748. })
  749. })
  750. w.loadURL('about:blank')
  751. })
  752. })
  753. })