chromium-spec.js 37 KB

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