chromium-spec.js 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255
  1. const assert = require('assert')
  2. const fs = require('fs')
  3. const http = require('http')
  4. const path = require('path')
  5. const ws = require('ws')
  6. const url = require('url')
  7. const ChildProcess = require('child_process')
  8. const {ipcRenderer, remote} = require('electron')
  9. const {closeWindow} = require('./window-helpers')
  10. const {app, BrowserWindow, ipcMain, protocol, session, webContents} = remote
  11. const isCI = remote.getGlobal('isCi')
  12. const features = process.atomBinding('features')
  13. /* Most of the APIs here don't use standard callbacks */
  14. /* eslint-disable standard/no-callback-literal */
  15. describe('chromium feature', () => {
  16. const fixtures = path.resolve(__dirname, 'fixtures')
  17. let listener = null
  18. let w = null
  19. afterEach(() => {
  20. if (listener != null) {
  21. window.removeEventListener('message', listener)
  22. }
  23. listener = null
  24. })
  25. describe('command line switches', () => {
  26. describe('--lang switch', () => {
  27. const currentLocale = app.getLocale()
  28. const testLocale = (locale, result, done) => {
  29. const appPath = path.join(__dirname, 'fixtures', 'api', 'locale-check')
  30. const electronPath = remote.getGlobal('process').execPath
  31. let output = ''
  32. let appProcess = ChildProcess.spawn(electronPath, [appPath, `--lang=${locale}`])
  33. appProcess.stdout.on('data', (data) => { output += data })
  34. appProcess.stdout.on('end', () => {
  35. output = output.replace(/(\r\n|\n|\r)/gm, '')
  36. assert.equal(output, result)
  37. done()
  38. })
  39. }
  40. it('should set the locale', (done) => testLocale('fr', 'fr', done))
  41. it('should not set an invalid locale', (done) => testLocale('asdfkl', currentLocale, done))
  42. })
  43. })
  44. afterEach(() => closeWindow(w).then(() => { w = null }))
  45. describe('heap snapshot', () => {
  46. it('does not crash', function () {
  47. process.atomBinding('v8_util').takeHeapSnapshot()
  48. })
  49. })
  50. describe('sending request of http protocol urls', () => {
  51. it('does not crash', (done) => {
  52. const server = http.createServer((req, res) => {
  53. res.end()
  54. server.close()
  55. done()
  56. })
  57. server.listen(0, '127.0.0.1', () => {
  58. const port = server.address().port
  59. $.get(`http://127.0.0.1:${port}`)
  60. })
  61. })
  62. })
  63. describe('accessing key names also used as Node.js module names', () => {
  64. it('does not crash', (done) => {
  65. w = new BrowserWindow({show: false})
  66. w.webContents.once('did-finish-load', () => { done() })
  67. w.webContents.once('crashed', () => done(new Error('WebContents crashed.')))
  68. w.loadURL(`file://${fixtures}/pages/external-string.html`)
  69. })
  70. })
  71. describe('loading jquery', () => {
  72. it('does not crash', (done) => {
  73. w = new BrowserWindow({
  74. show: false,
  75. webPreferences: {
  76. nodeIntegration: false
  77. }
  78. })
  79. w.webContents.once('did-finish-load', () => { done() })
  80. w.webContents.once('crashed', () => done(new Error('WebContents crashed.')))
  81. w.loadURL(`file://${fixtures}/pages/jquery.html`)
  82. })
  83. })
  84. describe('navigator.webkitGetUserMedia', () => {
  85. it('calls its callbacks', (done) => {
  86. navigator.webkitGetUserMedia({
  87. audio: true,
  88. video: false
  89. }, () => done(),
  90. () => done())
  91. })
  92. })
  93. describe('navigator.mediaDevices', () => {
  94. if (isCI) return
  95. it('can return labels of enumerated devices', (done) => {
  96. navigator.mediaDevices.enumerateDevices().then((devices) => {
  97. const labels = devices.map((device) => device.label)
  98. const labelFound = labels.some((label) => !!label)
  99. if (labelFound) {
  100. done()
  101. } else {
  102. done(new Error(`No device labels found: ${JSON.stringify(labels)}`))
  103. }
  104. }).catch(done)
  105. })
  106. it('can return new device id when cookie storage is cleared', (done) => {
  107. const options = {
  108. origin: null,
  109. storages: ['cookies']
  110. }
  111. const deviceIds = []
  112. const ses = session.fromPartition('persist:media-device-id')
  113. w = new BrowserWindow({
  114. show: false,
  115. webPreferences: {
  116. session: ses
  117. }
  118. })
  119. w.webContents.on('ipc-message', (event, args) => {
  120. if (args[0] === 'deviceIds') deviceIds.push(args[1])
  121. if (deviceIds.length === 2) {
  122. assert.notDeepEqual(deviceIds[0], deviceIds[1])
  123. closeWindow(w).then(() => {
  124. w = null
  125. done()
  126. }).catch((error) => done(error))
  127. } else {
  128. ses.clearStorageData(options, () => {
  129. w.webContents.reload()
  130. })
  131. }
  132. })
  133. w.loadURL(`file://${fixtures}/pages/media-id-reset.html`)
  134. })
  135. })
  136. describe('navigator.language', () => {
  137. it('should not be empty', () => {
  138. assert.notEqual(navigator.language, '')
  139. })
  140. })
  141. describe('navigator.languages', (done) => {
  142. it('should return the system locale only', () => {
  143. let appLocale = app.getLocale()
  144. assert.equal(navigator.languages.length, 1)
  145. assert.equal(navigator.languages[0], appLocale)
  146. })
  147. })
  148. describe('navigator.serviceWorker', () => {
  149. it('should register for file scheme', (done) => {
  150. w = new BrowserWindow({
  151. show: false,
  152. webPreferences: {
  153. partition: 'sw-file-scheme-spec'
  154. }
  155. })
  156. w.webContents.on('ipc-message', (event, args) => {
  157. if (args[0] === 'reload') {
  158. w.webContents.reload()
  159. } else if (args[0] === 'error') {
  160. done(args[1])
  161. } else if (args[0] === 'response') {
  162. assert.equal(args[1], 'Hello from serviceWorker!')
  163. session.fromPartition('sw-file-scheme-spec').clearStorageData({
  164. storages: ['serviceworkers']
  165. }, () => done())
  166. }
  167. })
  168. w.webContents.on('crashed', () => done(new Error('WebContents crashed.')))
  169. w.loadURL(`file://${fixtures}/pages/service-worker/index.html`)
  170. })
  171. it('should register for intercepted file scheme', (done) => {
  172. const customSession = session.fromPartition('intercept-file')
  173. customSession.protocol.interceptBufferProtocol('file', (request, callback) => {
  174. let file = url.parse(request.url).pathname
  175. if (file[0] === '/' && process.platform === 'win32') file = file.slice(1)
  176. const content = fs.readFileSync(path.normalize(file))
  177. const ext = path.extname(file)
  178. let type = 'text/html'
  179. if (ext === '.js') type = 'application/javascript'
  180. callback({data: content, mimeType: type})
  181. }, (error) => {
  182. if (error) done(error)
  183. })
  184. w = new BrowserWindow({
  185. show: false,
  186. webPreferences: { session: customSession }
  187. })
  188. w.webContents.on('ipc-message', (event, args) => {
  189. if (args[0] === 'reload') {
  190. w.webContents.reload()
  191. } else if (args[0] === 'error') {
  192. done(`unexpected error : ${args[1]}`)
  193. } else if (args[0] === 'response') {
  194. assert.equal(args[1], 'Hello from serviceWorker!')
  195. customSession.clearStorageData({
  196. storages: ['serviceworkers']
  197. }, () => {
  198. customSession.protocol.uninterceptProtocol('file', (error) => done(error))
  199. })
  200. }
  201. })
  202. w.webContents.on('crashed', () => done(new Error('WebContents crashed.')))
  203. w.loadURL(`file://${fixtures}/pages/service-worker/index.html`)
  204. })
  205. })
  206. describe('navigator.geolocation', () => {
  207. before(function () {
  208. if (!features.isFakeLocationProviderEnabled()) {
  209. return this.skip()
  210. }
  211. })
  212. it('returns position when permission is granted', (done) => {
  213. navigator.geolocation.getCurrentPosition((position) => {
  214. assert(position.timestamp)
  215. done()
  216. }, (error) => {
  217. done(error)
  218. })
  219. })
  220. it('returns error when permission is denied', (done) => {
  221. w = new BrowserWindow({
  222. show: false,
  223. webPreferences: {
  224. partition: 'geolocation-spec'
  225. }
  226. })
  227. w.webContents.on('ipc-message', (event, args) => {
  228. if (args[0] === 'success') {
  229. done()
  230. } else {
  231. done('unexpected response from geolocation api')
  232. }
  233. })
  234. w.webContents.session.setPermissionRequestHandler((wc, permission, callback) => {
  235. if (permission === 'geolocation') {
  236. callback(false)
  237. } else {
  238. callback(true)
  239. }
  240. })
  241. w.loadURL(`file://${fixtures}/pages/geolocation/index.html`)
  242. })
  243. })
  244. describe('window.open', () => {
  245. it('returns a BrowserWindowProxy object', () => {
  246. const b = window.open('about:blank', '', 'show=no')
  247. assert.equal(b.closed, false)
  248. assert.equal(b.constructor.name, 'BrowserWindowProxy')
  249. b.close()
  250. })
  251. it('accepts "nodeIntegration" as feature', (done) => {
  252. let b
  253. listener = (event) => {
  254. assert.equal(event.data.isProcessGlobalUndefined, true)
  255. b.close()
  256. done()
  257. }
  258. window.addEventListener('message', listener)
  259. b = window.open(`file://${fixtures}/pages/window-opener-node.html`, '', 'nodeIntegration=no,show=no')
  260. })
  261. it('inherit options of parent window', (done) => {
  262. let b
  263. listener = (event) => {
  264. const ref1 = remote.getCurrentWindow().getSize()
  265. const width = ref1[0]
  266. const height = ref1[1]
  267. assert.equal(event.data, `size: ${width} ${height}`)
  268. b.close()
  269. done()
  270. }
  271. window.addEventListener('message', listener)
  272. b = window.open(`file://${fixtures}/pages/window-open-size.html`, '', 'show=no')
  273. })
  274. for (const show of [true, false]) {
  275. it(`inherits parent visibility over parent {show=${show}} option`, (done) => {
  276. const w = new BrowserWindow({show})
  277. // toggle visibility
  278. if (show) {
  279. w.hide()
  280. } else {
  281. w.show()
  282. }
  283. w.webContents.once('new-window', (e, url, frameName, disposition, options) => {
  284. assert.equal(options.show, w.isVisible())
  285. w.close()
  286. done()
  287. })
  288. w.loadURL(`file://${fixtures}/pages/window-open.html`)
  289. })
  290. }
  291. it('disables node integration when it is disabled on the parent window', (done) => {
  292. let b
  293. listener = (event) => {
  294. assert.equal(event.data.isProcessGlobalUndefined, true)
  295. b.close()
  296. done()
  297. }
  298. window.addEventListener('message', listener)
  299. const windowUrl = require('url').format({
  300. pathname: `${fixtures}/pages/window-opener-no-node-integration.html`,
  301. protocol: 'file',
  302. query: {
  303. p: `${fixtures}/pages/window-opener-node.html`
  304. },
  305. slashes: true
  306. })
  307. b = window.open(windowUrl, '', 'nodeIntegration=no,show=no')
  308. })
  309. it('disables webviewTag when node integration is disabled on the parent window', (done) => {
  310. let b
  311. listener = (event) => {
  312. assert.equal(event.data.isWebViewUndefined, true)
  313. b.close()
  314. done()
  315. }
  316. window.addEventListener('message', listener)
  317. const windowUrl = require('url').format({
  318. pathname: `${fixtures}/pages/window-opener-no-web-view-tag.html`,
  319. protocol: 'file',
  320. query: {
  321. p: `${fixtures}/pages/window-opener-web-view.html`
  322. },
  323. slashes: true
  324. })
  325. b = window.open(windowUrl, '', 'nodeIntegration=no,show=no')
  326. })
  327. // TODO(alexeykuzmin): [Ch66] Fix the test.
  328. xit('disables node integration when it is disabled on the parent window for chrome devtools URLs', (done) => {
  329. let b
  330. app.once('web-contents-created', (event, contents) => {
  331. contents.once('did-finish-load', () => {
  332. contents.executeJavaScript('typeof process').then((typeofProcessGlobal) => {
  333. assert.equal(typeofProcessGlobal, 'undefined')
  334. b.close()
  335. done()
  336. }).catch(done)
  337. })
  338. })
  339. b = window.open('chrome-devtools://devtools/bundled/inspector.html', '', 'nodeIntegration=no,show=no')
  340. })
  341. it('disables JavaScript when it is disabled on the parent window', (done) => {
  342. let b
  343. app.once('web-contents-created', (event, contents) => {
  344. contents.once('did-finish-load', () => {
  345. app.once('browser-window-created', (event, window) => {
  346. const preferences = window.webContents.getLastWebPreferences()
  347. assert.equal(preferences.javascript, false)
  348. window.destroy()
  349. b.close()
  350. done()
  351. })
  352. // Click link on page
  353. contents.sendInputEvent({type: 'mouseDown', clickCount: 1, x: 1, y: 1})
  354. contents.sendInputEvent({type: 'mouseUp', clickCount: 1, x: 1, y: 1})
  355. })
  356. })
  357. const windowUrl = require('url').format({
  358. pathname: `${fixtures}/pages/window-no-javascript.html`,
  359. protocol: 'file',
  360. slashes: true
  361. })
  362. b = window.open(windowUrl, '', 'javascript=no,show=no')
  363. })
  364. it('disables the <webview> tag when it is disabled on the parent window', (done) => {
  365. let b
  366. listener = (event) => {
  367. assert.equal(event.data.isWebViewGlobalUndefined, true)
  368. b.close()
  369. done()
  370. }
  371. window.addEventListener('message', listener)
  372. const windowUrl = require('url').format({
  373. pathname: `${fixtures}/pages/window-opener-no-webview-tag.html`,
  374. protocol: 'file',
  375. query: {
  376. p: `${fixtures}/pages/window-opener-webview.html`
  377. },
  378. slashes: true
  379. })
  380. b = window.open(windowUrl, '', 'webviewTag=no,nodeIntegration=yes,show=no')
  381. })
  382. it('does not override child options', (done) => {
  383. let b
  384. const size = {
  385. width: 350,
  386. height: 450
  387. }
  388. listener = (event) => {
  389. assert.equal(event.data, `size: ${size.width} ${size.height}`)
  390. b.close()
  391. done()
  392. }
  393. window.addEventListener('message', listener)
  394. b = window.open(`file://${fixtures}/pages/window-open-size.html`, '', 'show=no,width=' + size.width + ',height=' + size.height)
  395. })
  396. it('handles cycles when merging the parent options into the child options', (done) => {
  397. w = BrowserWindow.fromId(ipcRenderer.sendSync('create-window-with-options-cycle'))
  398. w.loadURL(`file://${fixtures}/pages/window-open.html`)
  399. w.webContents.once('new-window', (event, url, frameName, disposition, options) => {
  400. assert.equal(options.show, false)
  401. assert.deepEqual(options.foo, {
  402. bar: null,
  403. baz: {
  404. hello: {
  405. world: true
  406. }
  407. },
  408. baz2: {
  409. hello: {
  410. world: true
  411. }
  412. }
  413. })
  414. done()
  415. })
  416. })
  417. it('defines a window.location getter', (done) => {
  418. let b
  419. let targetURL
  420. if (process.platform === 'win32') {
  421. targetURL = `file:///${fixtures.replace(/\\/g, '/')}/pages/base-page.html`
  422. } else {
  423. targetURL = `file://${fixtures}/pages/base-page.html`
  424. }
  425. app.once('browser-window-created', (event, window) => {
  426. window.webContents.once('did-finish-load', () => {
  427. assert.equal(b.location, targetURL)
  428. b.close()
  429. done()
  430. })
  431. })
  432. b = window.open(targetURL)
  433. })
  434. it('defines a window.location setter', (done) => {
  435. let b
  436. app.once('browser-window-created', (event, {webContents}) => {
  437. webContents.once('did-finish-load', () => {
  438. // When it loads, redirect
  439. b.location = `file://${fixtures}/pages/base-page.html`
  440. webContents.once('did-finish-load', () => {
  441. // After our second redirect, cleanup and callback
  442. b.close()
  443. done()
  444. })
  445. })
  446. })
  447. b = window.open('about:blank')
  448. })
  449. it('open a blank page when no URL is specified', (done) => {
  450. let b
  451. app.once('browser-window-created', (event, {webContents}) => {
  452. webContents.once('did-finish-load', () => {
  453. const {location} = b
  454. b.close()
  455. assert.equal(location, 'about:blank')
  456. let c
  457. app.once('browser-window-created', (event, {webContents}) => {
  458. webContents.once('did-finish-load', () => {
  459. const {location} = c
  460. c.close()
  461. assert.equal(location, 'about:blank')
  462. done()
  463. })
  464. })
  465. c = window.open('')
  466. })
  467. })
  468. b = window.open()
  469. })
  470. it('throws an exception when the arguments cannot be converted to strings', () => {
  471. assert.throws(() => {
  472. window.open('', {toString: null})
  473. }, /Cannot convert object to primitive value/)
  474. assert.throws(() => {
  475. window.open('', '', {toString: 3})
  476. }, /Cannot convert object to primitive value/)
  477. })
  478. it('sets the window title to the specified frameName', (done) => {
  479. let b
  480. app.once('browser-window-created', (event, createdWindow) => {
  481. assert.equal(createdWindow.getTitle(), 'hello')
  482. b.close()
  483. done()
  484. })
  485. b = window.open('', 'hello')
  486. })
  487. it('does not throw an exception when the frameName is a built-in object property', (done) => {
  488. let b
  489. app.once('browser-window-created', (event, createdWindow) => {
  490. assert.equal(createdWindow.getTitle(), '__proto__')
  491. b.close()
  492. done()
  493. })
  494. b = window.open('', '__proto__')
  495. })
  496. it('does not throw an exception when the features include webPreferences', () => {
  497. let b
  498. assert.doesNotThrow(() => {
  499. b = window.open('', '', 'webPreferences=')
  500. })
  501. b.close()
  502. })
  503. })
  504. describe('window.opener', () => {
  505. let url = `file://${fixtures}/pages/window-opener.html`
  506. it('is null for main window', (done) => {
  507. w = new BrowserWindow({ show: false })
  508. w.webContents.once('ipc-message', (event, args) => {
  509. assert.deepEqual(args, ['opener', null])
  510. done()
  511. })
  512. w.loadURL(url)
  513. })
  514. it('is not null for window opened by window.open', (done) => {
  515. let b
  516. listener = (event) => {
  517. assert.equal(event.data, 'object')
  518. b.close()
  519. done()
  520. }
  521. window.addEventListener('message', listener)
  522. b = window.open(url, '', 'show=no')
  523. })
  524. })
  525. describe('window.opener access from BrowserWindow', () => {
  526. const scheme = 'other'
  527. let url = `${scheme}://${fixtures}/pages/window-opener-location.html`
  528. let w = null
  529. before((done) => {
  530. protocol.registerFileProtocol(scheme, (request, callback) => {
  531. callback(`${fixtures}/pages/window-opener-location.html`)
  532. }, (error) => done(error))
  533. })
  534. after(() => {
  535. protocol.unregisterProtocol(scheme)
  536. })
  537. afterEach(() => {
  538. w.close()
  539. })
  540. it('does nothing when origin of current window does not match opener', (done) => {
  541. listener = (event) => {
  542. assert.equal(event.data, undefined)
  543. done()
  544. }
  545. window.addEventListener('message', listener)
  546. w = window.open(url, '', 'show=no,nodeIntegration=no')
  547. })
  548. it('works when origin matches', (done) => {
  549. listener = (event) => {
  550. assert.equal(event.data, location.href)
  551. done()
  552. }
  553. window.addEventListener('message', listener)
  554. w = window.open(`file://${fixtures}/pages/window-opener-location.html`, '', 'show=no,nodeIntegration=no')
  555. })
  556. it('works when origin does not match opener but has node integration', (done) => {
  557. listener = (event) => {
  558. assert.equal(event.data, location.href)
  559. done()
  560. }
  561. window.addEventListener('message', listener)
  562. w = window.open(url, '', 'show=no,nodeIntegration=yes')
  563. })
  564. })
  565. describe('window.opener access from <webview>', () => {
  566. const scheme = 'other'
  567. const srcPath = `${fixtures}/pages/webview-opener-postMessage.html`
  568. const pageURL = `file://${fixtures}/pages/window-opener-location.html`
  569. let webview = null
  570. before((done) => {
  571. protocol.registerFileProtocol(scheme, (request, callback) => {
  572. callback(srcPath)
  573. }, (error) => done(error))
  574. })
  575. after(() => {
  576. protocol.unregisterProtocol(scheme)
  577. })
  578. afterEach(() => {
  579. if (webview != null) webview.remove()
  580. })
  581. it('does nothing when origin of webview src URL does not match opener', (done) => {
  582. webview = new WebView()
  583. webview.addEventListener('console-message', (e) => {
  584. assert.equal(e.message, 'null')
  585. done()
  586. })
  587. webview.setAttribute('allowpopups', 'on')
  588. webview.src = url.format({
  589. pathname: srcPath,
  590. protocol: scheme,
  591. query: {
  592. p: pageURL
  593. },
  594. slashes: true
  595. })
  596. document.body.appendChild(webview)
  597. })
  598. it('works when origin matches', (done) => {
  599. webview = new WebView()
  600. webview.addEventListener('console-message', (e) => {
  601. assert.equal(e.message, webview.src)
  602. done()
  603. })
  604. webview.setAttribute('allowpopups', 'on')
  605. webview.src = url.format({
  606. pathname: srcPath,
  607. protocol: 'file',
  608. query: {
  609. p: pageURL
  610. },
  611. slashes: true
  612. })
  613. document.body.appendChild(webview)
  614. })
  615. it('works when origin does not match opener but has node integration', (done) => {
  616. webview = new WebView()
  617. webview.addEventListener('console-message', (e) => {
  618. webview.remove()
  619. assert.equal(e.message, webview.src)
  620. done()
  621. })
  622. webview.setAttribute('allowpopups', 'on')
  623. webview.setAttribute('nodeintegration', 'on')
  624. webview.src = url.format({
  625. pathname: srcPath,
  626. protocol: scheme,
  627. query: {
  628. p: pageURL
  629. },
  630. slashes: true
  631. })
  632. document.body.appendChild(webview)
  633. })
  634. })
  635. describe('window.postMessage', () => {
  636. it('sets the source and origin correctly', (done) => {
  637. let b
  638. listener = (event) => {
  639. window.removeEventListener('message', listener)
  640. b.close()
  641. const message = JSON.parse(event.data)
  642. assert.equal(message.data, 'testing')
  643. assert.equal(message.origin, 'file://')
  644. assert.equal(message.sourceEqualsOpener, true)
  645. assert.equal(event.origin, 'file://')
  646. done()
  647. }
  648. window.addEventListener('message', listener)
  649. app.once('browser-window-created', (event, {webContents}) => {
  650. webContents.once('did-finish-load', () => {
  651. b.postMessage('testing', '*')
  652. })
  653. })
  654. b = window.open(`file://${fixtures}/pages/window-open-postMessage.html`, '', 'show=no')
  655. })
  656. it('throws an exception when the targetOrigin cannot be converted to a string', () => {
  657. const b = window.open('')
  658. assert.throws(() => {
  659. b.postMessage('test', {toString: null})
  660. }, /Cannot convert object to primitive value/)
  661. b.close()
  662. })
  663. })
  664. describe('window.opener.postMessage', () => {
  665. it('sets source and origin correctly', (done) => {
  666. let b
  667. listener = (event) => {
  668. window.removeEventListener('message', listener)
  669. b.close()
  670. assert.equal(event.source, b)
  671. assert.equal(event.origin, 'file://')
  672. done()
  673. }
  674. window.addEventListener('message', listener)
  675. b = window.open(`file://${fixtures}/pages/window-opener-postMessage.html`, '', 'show=no')
  676. })
  677. it('supports windows opened from a <webview>', (done) => {
  678. const webview = new WebView()
  679. webview.addEventListener('console-message', (e) => {
  680. webview.remove()
  681. assert.equal(e.message, 'message')
  682. done()
  683. })
  684. webview.allowpopups = true
  685. webview.src = url.format({
  686. pathname: `${fixtures}/pages/webview-opener-postMessage.html`,
  687. protocol: 'file',
  688. query: {
  689. p: `${fixtures}/pages/window-opener-postMessage.html`
  690. },
  691. slashes: true
  692. })
  693. document.body.appendChild(webview)
  694. })
  695. describe('targetOrigin argument', () => {
  696. let serverURL
  697. let server
  698. beforeEach((done) => {
  699. server = http.createServer((req, res) => {
  700. res.writeHead(200)
  701. const filePath = path.join(fixtures, 'pages', 'window-opener-targetOrigin.html')
  702. res.end(fs.readFileSync(filePath, 'utf8'))
  703. })
  704. server.listen(0, '127.0.0.1', () => {
  705. serverURL = `http://127.0.0.1:${server.address().port}`
  706. done()
  707. })
  708. })
  709. afterEach(() => {
  710. server.close()
  711. })
  712. it('delivers messages that match the origin', (done) => {
  713. let b
  714. listener = (event) => {
  715. window.removeEventListener('message', listener)
  716. b.close()
  717. assert.equal(event.data, 'deliver')
  718. done()
  719. }
  720. window.addEventListener('message', listener)
  721. b = window.open(serverURL, '', 'show=no')
  722. })
  723. })
  724. })
  725. describe('creating a Uint8Array under browser side', () => {
  726. it('does not crash', () => {
  727. const RUint8Array = remote.getGlobal('Uint8Array')
  728. const arr = new RUint8Array()
  729. assert(arr)
  730. })
  731. })
  732. describe('webgl', () => {
  733. before(function () {
  734. if (isCI && process.platform === 'win32') {
  735. this.skip()
  736. }
  737. })
  738. it('can be get as context in canvas', () => {
  739. if (process.platform === 'linux') {
  740. // FIXME(alexeykuzmin): Skip the test.
  741. // this.skip()
  742. return
  743. }
  744. const webgl = document.createElement('canvas').getContext('webgl')
  745. assert.notEqual(webgl, null)
  746. })
  747. })
  748. describe('web workers', () => {
  749. it('Worker can work', (done) => {
  750. const worker = new Worker('../fixtures/workers/worker.js')
  751. const message = 'ping'
  752. worker.onmessage = (event) => {
  753. assert.equal(event.data, message)
  754. worker.terminate()
  755. done()
  756. }
  757. worker.postMessage(message)
  758. })
  759. it('Worker has no node integration by default', (done) => {
  760. let worker = new Worker('../fixtures/workers/worker_node.js')
  761. worker.onmessage = (event) => {
  762. assert.equal(event.data, 'undefined undefined undefined undefined')
  763. worker.terminate()
  764. done()
  765. }
  766. })
  767. it('Worker has node integration with nodeIntegrationInWorker', (done) => {
  768. let webview = new WebView()
  769. webview.addEventListener('ipc-message', (e) => {
  770. assert.equal(e.channel, 'object function object function')
  771. webview.remove()
  772. done()
  773. })
  774. webview.src = `file://${fixtures}/pages/worker.html`
  775. webview.setAttribute('webpreferences', 'nodeIntegration, nodeIntegrationInWorker')
  776. document.body.appendChild(webview)
  777. })
  778. it('SharedWorker can work', (done) => {
  779. const worker = new SharedWorker('../fixtures/workers/shared_worker.js')
  780. const message = 'ping'
  781. worker.port.onmessage = (event) => {
  782. assert.equal(event.data, message)
  783. done()
  784. }
  785. worker.port.postMessage(message)
  786. })
  787. it('SharedWorker has no node integration by default', (done) => {
  788. let worker = new SharedWorker('../fixtures/workers/shared_worker_node.js')
  789. worker.port.onmessage = (event) => {
  790. assert.equal(event.data, 'undefined undefined undefined undefined')
  791. done()
  792. }
  793. })
  794. it('SharedWorker has node integration with nodeIntegrationInWorker', (done) => {
  795. let webview = new WebView()
  796. webview.addEventListener('console-message', (e) => {
  797. console.log(e)
  798. })
  799. webview.addEventListener('ipc-message', (e) => {
  800. assert.equal(e.channel, 'object function object function')
  801. webview.remove()
  802. done()
  803. })
  804. webview.src = `file://${fixtures}/pages/shared_worker.html`
  805. webview.setAttribute('webpreferences', 'nodeIntegration, nodeIntegrationInWorker')
  806. document.body.appendChild(webview)
  807. })
  808. })
  809. describe('iframe', () => {
  810. let iframe = null
  811. beforeEach(() => {
  812. iframe = document.createElement('iframe')
  813. })
  814. afterEach(() => {
  815. document.body.removeChild(iframe)
  816. })
  817. it('does not have node integration', (done) => {
  818. iframe.src = `file://${fixtures}/pages/set-global.html`
  819. document.body.appendChild(iframe)
  820. iframe.onload = () => {
  821. assert.equal(iframe.contentWindow.test, 'undefined undefined undefined')
  822. done()
  823. }
  824. })
  825. })
  826. describe('storage', () => {
  827. it('requesting persitent quota works', (done) => {
  828. navigator.webkitPersistentStorage.requestQuota(1024 * 1024, (grantedBytes) => {
  829. assert.equal(grantedBytes, 1048576)
  830. done()
  831. })
  832. })
  833. describe('custom non standard schemes', () => {
  834. const protocolName = 'storage'
  835. let contents = null
  836. before((done) => {
  837. const handler = (request, callback) => {
  838. let parsedUrl = url.parse(request.url)
  839. let filename
  840. switch (parsedUrl.pathname) {
  841. case '/localStorage' : filename = 'local_storage.html'; break
  842. case '/sessionStorage' : filename = 'session_storage.html'; break
  843. case '/WebSQL' : filename = 'web_sql.html'; break
  844. case '/indexedDB' : filename = 'indexed_db.html'; break
  845. case '/cookie' : filename = 'cookie.html'; break
  846. default : filename = ''
  847. }
  848. callback({path: `${fixtures}/pages/storage/${filename}`})
  849. }
  850. protocol.registerFileProtocol(protocolName, handler, (error) => done(error))
  851. })
  852. after((done) => {
  853. protocol.unregisterProtocol(protocolName, () => done())
  854. })
  855. beforeEach(() => {
  856. contents = webContents.create({})
  857. })
  858. afterEach(() => {
  859. contents.destroy()
  860. contents = null
  861. })
  862. it('cannot access localStorage', (done) => {
  863. ipcMain.once('local-storage-response', (event, error) => {
  864. assert.equal(
  865. error,
  866. 'Failed to read the \'localStorage\' property from \'Window\': Access is denied for this document.')
  867. done()
  868. })
  869. contents.loadURL(protocolName + '://host/localStorage')
  870. })
  871. it('cannot access sessionStorage', (done) => {
  872. ipcMain.once('session-storage-response', (event, error) => {
  873. assert.equal(
  874. error,
  875. 'Failed to read the \'sessionStorage\' property from \'Window\': Access is denied for this document.')
  876. done()
  877. })
  878. contents.loadURL(`${protocolName}://host/sessionStorage`)
  879. })
  880. it('cannot access WebSQL database', (done) => {
  881. ipcMain.once('web-sql-response', (event, error) => {
  882. assert.equal(
  883. error,
  884. 'An attempt was made to break through the security policy of the user agent.')
  885. done()
  886. })
  887. contents.loadURL(`${protocolName}://host/WebSQL`)
  888. })
  889. it('cannot access indexedDB', (done) => {
  890. ipcMain.once('indexed-db-response', (event, error) => {
  891. assert.equal(error, 'The user denied permission to access the database.')
  892. done()
  893. })
  894. contents.loadURL(`${protocolName}://host/indexedDB`)
  895. })
  896. it('cannot access cookie', (done) => {
  897. ipcMain.once('cookie-response', (event, cookie) => {
  898. assert(!cookie)
  899. done()
  900. })
  901. contents.loadURL(`${protocolName}://host/cookie`)
  902. })
  903. })
  904. })
  905. describe('websockets', () => {
  906. let wss = null
  907. let server = null
  908. const WebSocketServer = ws.Server
  909. afterEach(() => {
  910. wss.close()
  911. server.close()
  912. })
  913. it('has user agent', (done) => {
  914. server = http.createServer()
  915. server.listen(0, '127.0.0.1', () => {
  916. const port = server.address().port
  917. wss = new WebSocketServer({ server: server })
  918. wss.on('error', done)
  919. wss.on('connection', (ws, upgradeReq) => {
  920. if (upgradeReq.headers['user-agent']) {
  921. done()
  922. } else {
  923. done('user agent is empty')
  924. }
  925. })
  926. const socket = new WebSocket(`ws://127.0.0.1:${port}`)
  927. assert(socket)
  928. })
  929. })
  930. })
  931. describe('Promise', () => {
  932. it('resolves correctly in Node.js calls', (done) => {
  933. document.registerElement('x-element', {
  934. prototype: Object.create(HTMLElement.prototype, {
  935. createdCallback: {
  936. value: () => {}
  937. }
  938. })
  939. })
  940. setImmediate(() => {
  941. let called = false
  942. Promise.resolve().then(() => {
  943. done(called ? void 0 : new Error('wrong sequence'))
  944. })
  945. document.createElement('x-element')
  946. called = true
  947. })
  948. })
  949. it('resolves correctly in Electron calls', (done) => {
  950. document.registerElement('y-element', {
  951. prototype: Object.create(HTMLElement.prototype, {
  952. createdCallback: {
  953. value: () => {}
  954. }
  955. })
  956. })
  957. remote.getGlobal('setImmediate')(() => {
  958. let called = false
  959. Promise.resolve().then(() => {
  960. done(called ? void 0 : new Error('wrong sequence'))
  961. })
  962. document.createElement('y-element')
  963. called = true
  964. })
  965. })
  966. })
  967. describe('fetch', () => {
  968. it('does not crash', (done) => {
  969. const server = http.createServer((req, res) => {
  970. res.end('test')
  971. server.close()
  972. })
  973. server.listen(0, '127.0.0.1', () => {
  974. const port = server.address().port
  975. fetch(`http://127.0.0.1:${port}`).then((res) => res.body.getReader())
  976. .then((reader) => {
  977. reader.read().then((r) => {
  978. reader.cancel()
  979. done()
  980. })
  981. }).catch((e) => done(e))
  982. })
  983. })
  984. })
  985. describe('PDF Viewer', () => {
  986. before(function () {
  987. if (!features.isPDFViewerEnabled()) {
  988. return this.skip()
  989. }
  990. })
  991. beforeEach(() => {
  992. this.pdfSource = url.format({
  993. pathname: path.join(fixtures, 'assets', 'cat.pdf').replace(/\\/g, '/'),
  994. protocol: 'file',
  995. slashes: true
  996. })
  997. this.pdfSourceWithParams = url.format({
  998. pathname: path.join(fixtures, 'assets', 'cat.pdf').replace(/\\/g, '/'),
  999. query: {
  1000. a: 1,
  1001. b: 2
  1002. },
  1003. protocol: 'file',
  1004. slashes: true
  1005. })
  1006. this.createBrowserWindow = ({plugins, preload}) => {
  1007. w = new BrowserWindow({
  1008. show: false,
  1009. webPreferences: {
  1010. preload: path.join(fixtures, 'module', preload),
  1011. plugins: plugins
  1012. }
  1013. })
  1014. }
  1015. this.testPDFIsLoadedInSubFrame = (page, preloadFile, done) => {
  1016. const pagePath = url.format({
  1017. pathname: path.join(fixtures, 'pages', page).replace(/\\/g, '/'),
  1018. protocol: 'file',
  1019. slashes: true
  1020. })
  1021. this.createBrowserWindow({plugins: true, preload: preloadFile})
  1022. ipcMain.once('pdf-loaded', (event, state) => {
  1023. assert.equal(state, 'success')
  1024. done()
  1025. })
  1026. w.webContents.on('page-title-updated', () => {
  1027. const parsedURL = url.parse(w.webContents.getURL(), true)
  1028. assert.equal(parsedURL.protocol, 'chrome:')
  1029. assert.equal(parsedURL.hostname, 'pdf-viewer')
  1030. assert.equal(parsedURL.query.src, pagePath)
  1031. assert.equal(w.webContents.getTitle(), 'cat.pdf')
  1032. })
  1033. w.webContents.loadURL(pagePath)
  1034. }
  1035. })
  1036. it('opens when loading a pdf resource as top level navigation', (done) => {
  1037. this.createBrowserWindow({plugins: true, preload: 'preload-pdf-loaded.js'})
  1038. ipcMain.once('pdf-loaded', (event, state) => {
  1039. assert.equal(state, 'success')
  1040. done()
  1041. })
  1042. w.webContents.on('page-title-updated', () => {
  1043. const parsedURL = url.parse(w.webContents.getURL(), true)
  1044. assert.equal(parsedURL.protocol, 'chrome:')
  1045. assert.equal(parsedURL.hostname, 'pdf-viewer')
  1046. assert.equal(parsedURL.query.src, this.pdfSource)
  1047. assert.equal(w.webContents.getTitle(), 'cat.pdf')
  1048. })
  1049. w.webContents.loadURL(this.pdfSource)
  1050. })
  1051. it('opens a pdf link given params, the query string should be escaped', (done) => {
  1052. this.createBrowserWindow({plugins: true, preload: 'preload-pdf-loaded.js'})
  1053. ipcMain.once('pdf-loaded', (event, state) => {
  1054. assert.equal(state, 'success')
  1055. done()
  1056. })
  1057. w.webContents.on('page-title-updated', () => {
  1058. const parsedURL = url.parse(w.webContents.getURL(), true)
  1059. assert.equal(parsedURL.protocol, 'chrome:')
  1060. assert.equal(parsedURL.hostname, 'pdf-viewer')
  1061. assert.equal(parsedURL.query.src, this.pdfSourceWithParams)
  1062. assert.equal(parsedURL.query.b, undefined)
  1063. assert(parsedURL.search.endsWith('%3Fa%3D1%26b%3D2'))
  1064. assert.equal(w.webContents.getTitle(), 'cat.pdf')
  1065. })
  1066. w.webContents.loadURL(this.pdfSourceWithParams)
  1067. })
  1068. it('should download a pdf when plugins are disabled', (done) => {
  1069. this.createBrowserWindow({plugins: false, preload: 'preload-pdf-loaded.js'})
  1070. ipcRenderer.sendSync('set-download-option', false, false)
  1071. ipcRenderer.once('download-done', (event, state, url, mimeType, receivedBytes, totalBytes, disposition, filename) => {
  1072. assert.equal(state, 'completed')
  1073. assert.equal(filename, 'cat.pdf')
  1074. assert.equal(mimeType, 'application/pdf')
  1075. fs.unlinkSync(path.join(fixtures, 'mock.pdf'))
  1076. done()
  1077. })
  1078. w.webContents.loadURL(this.pdfSource)
  1079. })
  1080. it('should not open when pdf is requested as sub resource', (done) => {
  1081. fetch(this.pdfSource).then((res) => {
  1082. assert.equal(res.status, 200)
  1083. assert.notEqual(document.title, 'cat.pdf')
  1084. done()
  1085. }).catch((e) => done(e))
  1086. })
  1087. it('opens when loading a pdf resource in a iframe', (done) => {
  1088. this.testPDFIsLoadedInSubFrame('pdf-in-iframe.html', 'preload-pdf-loaded-in-subframe.js', done)
  1089. })
  1090. it('opens when loading a pdf resource in a nested iframe', (done) => {
  1091. this.testPDFIsLoadedInSubFrame('pdf-in-nested-iframe.html', 'preload-pdf-loaded-in-nested-subframe.js', done)
  1092. })
  1093. })
  1094. describe('window.alert(message, title)', () => {
  1095. it('throws an exception when the arguments cannot be converted to strings', () => {
  1096. assert.throws(() => {
  1097. window.alert({toString: null})
  1098. }, /Cannot convert object to primitive value/)
  1099. })
  1100. })
  1101. describe('window.confirm(message, title)', () => {
  1102. it('throws an exception when the arguments cannot be converted to strings', () => {
  1103. assert.throws(() => {
  1104. window.confirm({toString: null}, 'title')
  1105. }, /Cannot convert object to primitive value/)
  1106. })
  1107. })
  1108. describe('window.history', () => {
  1109. describe('window.history.go(offset)', () => {
  1110. it('throws an exception when the argumnet cannot be converted to a string', () => {
  1111. assert.throws(() => {
  1112. window.history.go({toString: null})
  1113. }, /Cannot convert object to primitive value/)
  1114. })
  1115. })
  1116. describe('window.history.pushState', () => {
  1117. it('should push state after calling history.pushState() from the same url', (done) => {
  1118. w = new BrowserWindow({ show: false })
  1119. w.webContents.once('did-finish-load', () => {
  1120. // History should have current page by now.
  1121. assert.equal(w.webContents.length(), 1)
  1122. w.webContents.executeJavaScript('window.history.pushState({}, "")', () => {
  1123. // Initial page + pushed state
  1124. assert.equal(w.webContents.length(), 2)
  1125. done()
  1126. })
  1127. })
  1128. w.loadURL('about:blank')
  1129. })
  1130. })
  1131. })
  1132. })