api-web-contents-spec.js 26 KB

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